diff --git a/.editorconfig b/.editorconfig index 560067027c52e..1e8be1006aa96 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 043c3ac878abc..be8582a6b221d 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 64a2e6494c1b1..a88773308d5e6 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/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 7edfa03584c1c..e728d741d90d7 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -2,7 +2,7 @@ name: Trigger wheel build on: push: - branches: [master, 'release*'] + branches: [main, master, 'release*'] tags: ['*'] jobs: @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 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 a3294c08a79ca..5dc86a1159f45 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,7 +3,7 @@ name: Check documentation build on: workflow_dispatch: push: - branches: [master, 'release*'] + branches: [main, master, 'release*'] tags: ['*'] pull_request: paths: @@ -20,11 +20,12 @@ jobs: env: TOXENV: docs TOX_SKIP_MISSING_INTERPRETERS: False + VERIFY_MYPY_ERROR_CODES: 1 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: '3.7' + python-version: '3.8' - name: Install tox run: pip install --upgrade 'setuptools!=50' tox==4.4.4 - name: Setup tox environment diff --git a/.github/workflows/mypy_primer_comment.yml b/.github/workflows/mypy_primer_comment.yml index 12ce91c129109..6e3bb590364f0 100644 --- a/.github/workflows/mypy_primer_comment.yml +++ b/.github/workflows/mypy_primer_comment.yml @@ -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/test.yml b/.github/workflows/test.yml index ed0c82ef5fa11..f594353ed05a1 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: @@ -26,75 +26,78 @@ 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" - - name: Test suite with py39-ubuntu - python: '3.9' + test_mypyc: true + - name: Test suite with py38-windows-64 + python: '3.8' arch: x64 - os: ubuntu-latest - toxenv: py + os: windows-latest + toxenv: py38 tox_extra_args: "-n 2" - - name: Test suite with py37-ubuntu, mypyc-compiled - python: '3.7' + - name: Test suite with py39-ubuntu + python: '3.9' arch: x64 os: ubuntu-latest toxenv: py tox_extra_args: "-n 2" - test_mypyc: true - - name: Test suite with py310-ubuntu, mypyc-compiled + - name: Test suite with py310-ubuntu python: '3.10' arch: x64 os: ubuntu-latest toxenv: py tox_extra_args: "-n 2" - test_mypyc: true - - name: Test suite with py310-ubuntu - python: '3.10' + - name: Test suite with py311-ubuntu, mypyc-compiled + python: '3.11' arch: x64 os: ubuntu-latest toxenv: py tox_extra_args: "-n 2" - - name: Test suite with py311-ubuntu, mypyc-compiled - python: '3.11' + test_mypyc: true + - name: Test suite with py312-ubuntu, mypyc-compiled + python: '3.12-dev' arch: x64 os: ubuntu-latest toxenv: py tox_extra_args: "-n 2" test_mypyc: true - - name: mypyc runtime tests with py37-macos - python: '3.7' + + - name: mypyc runtime tests with py38-macos + python: '3.8.17' arch: x64 os: macos-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' + - 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" 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 @@ -138,24 +141,6 @@ jobs: - 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 @@ -180,6 +165,7 @@ jobs: 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 \ diff --git a/.github/workflows/test_stubgenc.yml b/.github/workflows/test_stubgenc.yml index b48031e5c18f6..db9bf413faa36 100644 --- a/.github/workflows/test_stubgenc.yml +++ b/.github/workflows/test_stubgenc.yml @@ -3,7 +3,7 @@ name: Test stubgenc on pybind11-mypy-demo on: workflow_dispatch: push: - branches: [master, 'release*'] + branches: [main, master, 'release*'] tags: ['*'] pull_request: paths: diff --git a/.gitignore b/.gitignore index c6761f0ed7365..6c35e3d89342d 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 0de686b7eb011..a56e1af938b8c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,16 +1,16 @@ +exclude: '^(mypyc/external/)|(mypy/typeshed/)' # 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.4.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/hauntsaninja/black-pre-commit-mirror + rev: 23.3.0 # 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 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.272 # 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] diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000000000..8ec33ee641ed6 --- /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/CONTRIBUTING.md b/CONTRIBUTING.md index 2b2e6cdb9734c..82e55f437e872 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,32 +12,42 @@ 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 -``` +#### (3) Create then activate a virtual environment + +```bash # On Windows, the commands may be slightly different. For more details, see # https://docs.python.org/3/library/venv.html#creating-virtual-environments python3 -m venv venv source venv/bin/activate ``` -#### (3) Install the test requirements and the project -``` +#### (4) Install the test requirements and the project + +```bash python3 -m pip install -r test-requirements.txt python3 -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,12 +57,14 @@ your PR. However, if you wish to do so, you can run the full test suite like this: -``` + +```bash python3 runtests.py ``` You can also use `tox` to run tests (`tox` handles setting up the test environment for you): -``` + +```bash tox run -e py # Or some specific python version: @@ -63,6 +75,7 @@ tox run -e lint ``` Some useful commands for running specific tests include: + ```bash # Use mypy to check mypy's own code python3 runtests.py self @@ -75,11 +88,8 @@ 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, @@ -90,6 +100,7 @@ see [the README in the test-data directory](test-data/unit/README.md). 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 +151,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 +158,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/README.md b/README.md index 6c9f01968f920..8b1ebbc0f2cba 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,10 +40,10 @@ 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) - 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 52c518d53bc2a..aac1b95eddf7b 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 0bd7b6a38031e..4454b02e7f3ad 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 be69e9d882812..c87c4c1abcb2c 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/make.bat b/docs/make.bat index 1e3d843201740..3664bed34b7e6 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/source/additional_features.rst b/docs/source/additional_features.rst index ef5bf9e8936df..5dd136476eaa8 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 @@ -121,55 +120,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 +176,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 5aa1770512b85..297427e72aca2 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 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 @@ -177,8 +178,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) @@ -259,6 +258,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 +295,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 +309,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 +359,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 1d4164192318a..73f95f1c56583 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 diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index 31d23db204ebe..d9de5cd8f9bd3 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -353,12 +353,17 @@ definitions or calls. .. 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 @@ -607,6 +612,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 diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index afb8e7d3ffe18..e6570b7eef5b3 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -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. diff --git a/docs/source/conf.py b/docs/source/conf.py index 5faefdc92ed1c..683b2a6785b32 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"] @@ -267,8 +267,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 3b96e6bd7a5a5..9e79ff99937b6 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -3,17 +3,22 @@ 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 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 +108,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. @@ -361,7 +366,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`). @@ -493,14 +498,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 diff --git a/docs/source/error_code_list.rst b/docs/source/error_code_list.rst index 0388cd2165dd2..f935e025e589a 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,14 +521,16 @@ 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: @@ -477,13 +547,18 @@ 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 @@ -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,6 +646,8 @@ the issue: def set_y(self) -> None: self.y: int = self.x # Added annotation here +.. _code-import: + Check that import target can be found [import] ---------------------------------------------- @@ -584,6 +663,8 @@ Example: See :ref:`ignore-missing-imports` for how to work around these errors. +.. _code-no-redef: + Check that each name is defined once [no-redef] ----------------------------------------------- @@ -610,6 +691,8 @@ Example: # (the first definition wins!) A('x') +.. _code-func-returns-value: + Check that called function returns a value [func-returns-value] --------------------------------------------------------------- @@ -632,6 +715,8 @@ returns ``None``: if f(): print("not false") +.. _code-abstract: + Check instantiation of abstract classes [abstract] -------------------------------------------------- @@ -663,6 +748,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 +776,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,6 +800,8 @@ 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] ------------------------------------------- @@ -735,6 +826,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] ----------------------------------------------- @@ -791,6 +884,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 +899,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 +930,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 +954,8 @@ implementation. def func(value): pass # actual implementation +.. _code-unused-coroutine: + Check that coroutine return value is used [unused-coroutine] ------------------------------------------------------------ @@ -878,6 +979,30 @@ 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] + + def g() -> None: + # This is a blocker error and cannot be silenced. + await f() # Error: "await" outside coroutine ("async def") + +.. _code-assert-type: + Check types in assert_type [assert-type] ---------------------------------------- @@ -892,6 +1017,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 +1032,71 @@ 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-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-syntax: + Report syntax errors [syntax] ----------------------------- @@ -912,6 +1104,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 f160515f0a9e1..30fad0793771f 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,88 @@ 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 diff --git a/docs/source/error_codes.rst b/docs/source/error_codes.rst index 34bb8ab6b5e1a..65ae0e5816e83 100644 --- a/docs/source/error_codes.rst +++ b/docs/source/error_codes.rst @@ -32,7 +32,7 @@ or config ``hide_error_codes = True`` to hide error codes. Error codes are shown 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. +See :ref:`ignore-without-code` for more information. .. _silence-error-codes: @@ -114,13 +114,13 @@ 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. diff --git a/docs/source/existing_code.rst b/docs/source/existing_code.rst index 410d7af0c3509..c66008f4b782c 100644 --- a/docs/source/existing_code.rst +++ b/docs/source/existing_code.rst @@ -183,7 +183,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 +191,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 daf8636163344..506f548db687e 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. diff --git a/docs/source/faq.rst b/docs/source/faq.rst index d97929c2cfa61..195805382cd3b 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/getting_started.rst b/docs/source/getting_started.rst index 9b927097cfd20..463c73b2fe761 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 @@ -264,7 +264,7 @@ Python standard library. For example, here is a function which uses the 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 0000000000000..3064833b5631f --- /dev/null +++ b/docs/source/html_builder.py @@ -0,0 +1,62 @@ +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 + + +class MypyHTMLBuilder(StandaloneHTMLBuilder): + def __init__(self, app: Sphinx) -> None: + super().__init__(app) + 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 7ab3edebad397..c9dc6bc1f8c99 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 b9a3b891c99cb..fa4fae1a0b3e5 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 b575a6eac4c57..c3180850f1193 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 diff --git a/docs/source/more_types.rst b/docs/source/more_types.rst index ff5e8d3843516..542ff1c57c71d 100644 --- a/docs/source/more_types.rst +++ b/docs/source/more_types.rst @@ -824,11 +824,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 +839,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:: - - :ref:`reveal_type() ` displays the inferred static type of - an expression. - -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 +.. _async-iterators: - 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!" +Asynchronous iterators +---------------------- - 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 +889,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 ec12283ea3bb6..7586026b6c812 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 automatically enables ``--local-partial-types`` by default. + + Daemon client commands ********************** diff --git a/docs/source/protocols.rst b/docs/source/protocols.rst index cb51809a66d50..3336d77cb3978 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 +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 ***************************** @@ -89,18 +86,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 +162,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 ********************************* @@ -319,7 +317,7 @@ 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: diff --git a/docs/source/runtime_troubles.rst b/docs/source/runtime_troubles.rst index a62652111de66..909215a774a9d 100644 --- a/docs/source/runtime_troubles.rst +++ b/docs/source/runtime_troubles.rst @@ -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 ----------------------------- @@ -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/stubtest.rst b/docs/source/stubtest.rst index f3c036f56c06d..59889252f0569 100644 --- a/docs/source/stubtest.rst +++ b/docs/source/stubtest.rst @@ -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 72a8166791401..4bc0fda70138d 100644 --- a/docs/source/type_narrowing.rst +++ b/docs/source/type_narrowing.rst @@ -271,7 +271,7 @@ Generic TypeGuards else: reveal_type(names) # tuple[str, ...] -Typeguards with parameters +TypeGuards with parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~ Type guard functions can accept extra arguments: @@ -293,7 +293,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 diff --git a/misc/analyze_cache.py b/misc/analyze_cache.py index 8b805d8da0bcc..33205f5132fc3 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/build-debug-python.sh b/misc/build-debug-python.sh index f652d6ad9937d..8dd1bff4c9ed1 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/convert-cache.py b/misc/convert-cache.py index e5da9c2650d59..2a8a9579c11bd 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/fix_annotate.py b/misc/fix_annotate.py index 7fffba8a85079..fc8ac27466d50 100644 --- a/misc/fix_annotate.py +++ b/misc/fix_annotate.py @@ -38,7 +38,6 @@ def foo(self, bar, baz=12): class FixAnnotate(BaseFix): - # This fixer is compatible with the bottom matcher. BM_compatible = True diff --git a/misc/incremental_checker.py b/misc/incremental_checker.py index 85239b6462b88..4e42aef333bb4 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/proper_plugin.py b/misc/proper_plugin.py index a8a8e80ef360a..a6e6dc03b6256 100644 --- a/misc/proper_plugin.py +++ b/misc/proper_plugin.py @@ -88,6 +88,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", diff --git a/misc/remove-eol-whitespace.sh b/misc/remove-eol-whitespace.sh index 3da6b9de64a55..5cf666997e343 100644 --- a/misc/remove-eol-whitespace.sh +++ b/misc/remove-eol-whitespace.sh @@ -3,6 +3,6 @@ # 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 +# https://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 86b0fd774e0c3..3f870d574d38f 100644 --- a/misc/sync-typeshed.py +++ b/misc/sync-typeshed.py @@ -179,12 +179,27 @@ def main() -> None: print("Created typeshed sync commit.") commits_to_cherry_pick = [ - "874afd970", # LiteralString reverts - "3a240111e", # sum reverts - "f968d6ce0", # ctypes reverts + "9f4c0d8af", # LiteralString reverts + "56f434336", # sum reverts + "71c4269df", # ctypes reverts + "186fbb18e", # ParamSpec for functools.wraps ] for commit in commits_to_cherry_pick: - subprocess.run(["git", "cherry-pick", commit], check=True) + try: + subprocess.run(["git", "cherry-pick", commit], check=True) + except subprocess.CalledProcessError: + if not sys.__stdin__.isatty(): + # We're in an automated context + raise + + # Allow the option to merge manually + print( + f"Commit {commit} failed to cherry pick." + " In a separate shell, please manually merge and continue cherry pick." + ) + rsp = input("Did you finish the cherry pick? [y/N]: ") + if rsp.lower() not in {"y", "yes"}: + raise print(f"Cherry-picked {commit}.") if args.make_pr: diff --git a/mypy-requirements.txt b/mypy-requirements.txt index 9a55446eb05ab..f81412be761e8 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 589bfbbfa1a7c..e2179dba30ca3 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 a81ed3cd1f161..55a51d4adbb6a 100644 --- a/mypy/applytype.py +++ b/mypy/applytype.py @@ -8,6 +8,7 @@ from mypy.types import ( AnyType, CallableType, + Instance, Parameters, ParamSpecType, PartialType, @@ -75,7 +76,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. @@ -110,7 +110,7 @@ def apply_generic_arguments( 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): + if isinstance(nt, (CallableType, Parameters)): callable = callable.expand_param_spec(nt) # Apply arguments to argument types. @@ -119,15 +119,9 @@ def apply_generic_arguments( 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] - ] + [expand_type(at, id_to_type) 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 :] - ] + + [expand_type(at, id_to_type) for at in callable.arg_types[star_index + 1 :]] ) ) @@ -155,22 +149,30 @@ def apply_generic_arguments( 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) + if isinstance(expanded_tvt, list): + for t in expanded_tvt: + assert not isinstance(t, UnpackType) + callable = replace_starargs(callable, expanded_tvt) + else: + assert isinstance(expanded_tvt, Instance) + assert expanded_tvt.type.fullname == "builtins.tuple" + callable = callable.copy_modified( + arg_types=( + callable.arg_types[:star_index] + + [expanded_tvt.args[0]] + + callable.arg_types[star_index + 1 :] + ) + ) else: assert False, "mypy bug: unhandled case applying unpack" 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. 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 @@ -178,7 +180,7 @@ def apply_generic_arguments( remaining_tvars = [tv for tv in tvars if tv.id not in id_to_type] 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, ) diff --git a/mypy/binder.py b/mypy/binder.py index d822aecec2f34..37c0b6bb90060 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -51,6 +51,9 @@ def __init__(self, id: int, conditional_frame: bool = False) -> None: # 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 +66,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] @@ -446,6 +449,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: diff --git a/mypy/build.py b/mypy/build.py index a4817d1866c7e..5a0a481ae1a2f 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -31,22 +31,20 @@ 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.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 @@ -116,7 +114,10 @@ "types", "typing_extensions", "mypy_extensions", - "_importlib_modulespec", + "_typeshed", + "_collections_abc", + "collections", + "collections.abc", "sys", "abc", } @@ -235,17 +236,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 @@ -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: @@ -669,8 +662,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( @@ -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) @@ -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 = [] @@ -2245,6 +2239,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 +2405,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 +2574,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 +2638,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: @@ -2941,6 +2945,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 @@ -3072,7 +3077,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 +3165,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 +3298,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. @@ -3461,15 +3466,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 +3492,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 +3520,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 4bf009f74092e..f2873c7d58e40 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -9,6 +9,7 @@ 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, @@ -41,7 +42,7 @@ 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.message_registry import ErrorMessage @@ -134,7 +135,9 @@ is_final_node, ) from mypy.options import 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 @@ -151,7 +154,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 ( @@ -211,11 +214,8 @@ get_proper_types, is_literal_type, is_named_instance, - is_optional, - remove_optional, - store_argument_type, - strip_type, ) +from mypy.types_utils import is_optional, 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 @@ -419,7 +419,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 +464,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 +483,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 ) @@ -629,7 +631,8 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: 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) @@ -640,7 +643,14 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: if defn.impl: defn.impl.accept(self) if defn.info: - self.check_method_override(defn) + 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) if not defn.is_property: self.check_overlapping_overloads(defn) @@ -967,7 +977,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 +1052,9 @@ def check_func_item( if name == "__exit__": self.check__exit__return_type(defn) + 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]: @@ -1156,11 +1170,10 @@ def check_func_def( 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): @@ -1177,7 +1190,8 @@ def check_func_def( msg = None elif typ.arg_names[i] in {"self", "cls"}: msg = message_registry.ERASED_SELF_TYPE_NOT_SUPERTYPE.format( - erased, ref_type + erased.str_with_options(self.options), + ref_type.str_with_options(self.options), ) else: msg = message_registry.MISSING_OR_INVALID_SELF_TYPE @@ -1186,9 +1200,10 @@ def check_func_def( 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,6 +1218,21 @@ 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 # restrictions: we only want to report a warning if a certain statement is @@ -1214,6 +1244,8 @@ def check_func_def( 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 +1338,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 +1371,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 +1478,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 +1552,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] @@ -1753,31 +1815,58 @@ 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__") + ): + 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) @@ -1788,13 +1877,14 @@ def check_method_or_accessor_override_for_base( # 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 +1892,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,23 +1967,6 @@ 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) @@ -1943,7 +2017,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 +2040,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): @@ -2067,7 +2165,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): @@ -2347,7 +2444,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 +2472,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: @@ -2484,7 +2584,14 @@ 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): + # 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__' @@ -2576,9 +2683,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 +2707,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 +2723,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 +2815,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 +2844,9 @@ 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__": + if dataclasses_plugin.is_processed_dataclass(self.scope.active_class()): + self.fail(message_registry.DATACLASS_POST_INIT_MUST_BE_A_FUNCTION, rvalue) # Defer PartialType's super type checking. if ( @@ -2947,7 +3059,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: @@ -3284,7 +3395,7 @@ 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 # control in cases like: a, b = [int, str] where rhs would get @@ -3299,14 +3410,14 @@ def check_assignment_to_multiple_lvalues( rvalues.extend([TempNode(typ) for typ in typs.items]) elif 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: @@ -3632,8 +3743,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 +3769,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 +3783,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, @@ -3967,7 +4080,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 +4165,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 +4173,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 +4239,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) @@ -4182,6 +4305,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 @@ -4643,7 +4767,14 @@ def visit_decorator(self, e: Decorator) -> None: self.check_incompatible_property_override(e) # For overloaded functions we already checked override for overload as a whole. 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)): @@ -4948,7 +5079,9 @@ 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" + ) try: info, full_name = _make_fake_typeinfo_and_full_name(base_classes, curr_module) with self.msg.filter_errors() as local_errors: @@ -4980,11 +5113,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 +5165,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 +5199,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 +5214,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 +5250,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 +5324,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 +5338,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, ) @@ -5803,7 +5942,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 = [] @@ -5999,7 +6145,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: @@ -6192,7 +6340,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 +6355,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 +6534,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")) @@ -6501,7 +6646,7 @@ def conditional_types_with_intersection( 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 +6662,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 @@ -6688,6 +6834,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.""" @@ -6768,6 +6917,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) @@ -6955,7 +7105,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) @@ -7085,6 +7235,7 @@ def detach_callable(typ: CallableType) -> CallableType: id=var.id, values=var.values, upper_bound=var.upper_bound, + default=var.default, variance=var.variance, ) ) @@ -7112,7 +7263,6 @@ 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 ) @@ -7340,7 +7490,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 +7601,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 +7624,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) @@ -7590,3 +7740,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 38b5c2419d953..46a5e35f320df 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -4,15 +4,16 @@ 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, 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 @@ -98,12 +99,19 @@ ) 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, has_any_from_unimported_type, + instantiate_type_alias, make_optional_type, set_any_tvars, ) @@ -114,6 +122,7 @@ false_only, fixup_partial_type, function_type, + get_type_vars, is_literal_type_like, make_simplified_union, simple_literal_type, @@ -146,21 +155,21 @@ TypedDictType, TypeOfAny, TypeType, + TypeVarLikeType, TypeVarTupleType, TypeVarType, UninhabitedType, UnionType, UnpackType, + flatten_nested_tuples, flatten_nested_unions, get_proper_type, get_proper_types, has_recursive_types, - is_generic_instance, is_named_instance, - is_optional, - is_self_type_like, - remove_optional, + split_with_prefix_and_suffix, ) +from mypy.types_utils import is_generic_instance, is_optional, 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 @@ -170,7 +179,7 @@ # 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 @@ -301,6 +310,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 = self.chk.options.new_type_inference def reset(self) -> None: self.resolved_type = {} @@ -686,74 +696,183 @@ 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: - item_names.append(literal_value) - return dict(zip(item_names, item_args)) + 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. + """ + with self.chk.local_type_map(), self.msg.filter_errors(): + 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: + # 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) @@ -794,20 +913,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 +966,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 +984,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 @@ -1792,6 +1932,51 @@ 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 self.chk.options.new_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 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 = infer_function_type_arguments( + callee_type, + arg_types, + arg_kinds, + formal_to_actual, + context=self.argument_infer_context(), + strict=self.chk.in_checked_function(), + allow_polymorphic=True, + ) + for i, pa in enumerate(get_proper_types(poly_inferred_args)): + if isinstance(pa, (NoneType, UninhabitedType)) or has_erased_component(pa): + # Indicate that free variables should not be applied in the call below. + poly_inferred_args[i] = None + poly_callee_type = self.apply_generic_arguments( + callee_type, poly_inferred_args, context + ) + yes_vars = poly_callee_type.variables + no_vars = {v for v in callee_type.variables if v not in poly_callee_type.variables} + if not set(get_type_vars(poly_callee_type)) & no_vars: + # 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, yes_vars) + if applied is not None and poly_inferred_args != [UninhabitedType()] * len( + poly_inferred_args + ): + freeze_all_type_vars(applied) + return applied + # If it didn't work, erase free variables as , to avoid confusing errors. + inferred_args = [ + expand_type(a, {v.id: UninhabitedType() for v in callee_type.variables}) + if a is not None + else None + for a in inferred_args + ] else: # In dynamically typed functions use implicit 'Any' types for # type variables. @@ -2057,7 +2242,7 @@ def check_argument_types( 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) + and isinstance(first_actual_arg_type.items[0], UnpackType) ): # TODO: use walrus operator actual_types = [first_actual_arg_type.items[0]] + [ @@ -2084,7 +2269,7 @@ 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 @@ -2126,7 +2311,9 @@ 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 @@ -2165,15 +2352,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( @@ -2337,10 +2516,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] = [] @@ -2423,8 +2600,7 @@ def infer_overload_return_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. @@ -3015,7 +3191,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) @@ -3917,6 +4093,12 @@ def visit_assert_type_expr(self, expr: AssertTypeExpr) -> Type: always_allow_any=True, ) 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( @@ -3960,13 +4142,18 @@ def visit_type_application(self, tapp: TypeApplication) -> 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 +4218,12 @@ 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): @@ -4058,6 +4250,35 @@ 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 + if not vars or not any(isinstance(v, TypeVarTupleType) for v in vars): + return list(args) + + prefix = next(i for (i, v) in enumerate(vars) if isinstance(v, TypeVarTupleType)) + suffix = len(vars) - prefix - 1 + args = flatten_nested_tuples(args) + if len(args) < len(vars) - 1: + self.msg.incompatible_type_application(len(vars), len(args), ctx) + return [AnyType(TypeOfAny.from_error)] * len(vars) + + 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 +4292,28 @@ def apply_type_arguments_to_callable( tp = get_proper_type(tp) if isinstance(tp, CallableType): - if len(tp.variables) != len(args): + if len(tp.variables) != len(args) and not any( + isinstance(v, TypeVarTupleType) for v in tp.variables + ): 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) 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): + if len(it.variables) != len(args) and not any( + isinstance(v, TypeVarTupleType) for v in it.variables + ): self.msg.incompatible_type_application(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 +4367,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], @@ -4268,7 +4505,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 +4541,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 +4576,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 +4605,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. @@ -4457,7 +4692,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: @@ -4638,7 +4873,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 +4923,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 +4950,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 +5176,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 +5186,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) @@ -5124,6 +5377,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: @@ -5214,6 +5476,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, 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 + ) + def has_any_type(t: Type, ignore_in_type_obj: bool = False) -> bool: """Whether t contains an Any type""" @@ -5233,6 +5515,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 +5593,92 @@ 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: Sequence[TypeVarLikeType]) -> None: + self.poly_tvars = set(poly_tvars) + # This is a simplified version of TypeVarScope used during semantic analysis. + self.bound_tvars: set[TypeVarLikeType] = set() + self.seen_aliases: set[TypeInfo] = set() + + def visit_callable_type(self, t: CallableType) -> Type: + found_vars = set() + for arg in t.arg_types: + found_vars |= set(get_type_vars(arg)) & self.poly_tvars + + found_vars -= self.bound_tvars + self.bound_tvars |= found_vars + result = super().visit_callable_type(t) + self.bound_tvars -= found_vars + + assert isinstance(result, ProperType) and isinstance(result, CallableType) + result.variables = list(result.variables) + list(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: + # TODO: Support polymorphic apply for ParamSpec. + raise PolyTranslationError() + + def visit_type_var_tuple(self, t: TypeVarTupleType) -> Type: + # TODO: Support polymorphic apply for TypeVarTuple. + raise PolyTranslationError() + + 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: + # There is the same problem with callback protocols as with aliases + # (callback protocols are essentially more flexible aliases to callables). + # Note: consider supporting bindings in instances, e.g. LRUCache[[x: T], T]. + if t.args and t.type.is_protocol and t.type.protocol_members == ["__call__"]: + if t.type in self.seen_aliases: + raise PolyTranslationError() + self.seen_aliases.add(t.type) + call = find_member("__call__", t, t, is_operator=True) + assert call is not None + return call.accept(self) + return super().visit_instance(t) + + class ArgInferSecondPassQuery(types.BoolTypeQuery): """Query whether an argument type should be inferred in the second pass. @@ -5474,7 +5854,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:]) @@ -5518,7 +5898,7 @@ def merge_typevars_in_callables_by_name( 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 a2c580e134460..343dfe3de2431 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -312,17 +312,14 @@ 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 - else: + if not method.is_static: if name != "__call__": # TODO: use proper treatment of special methods on unions instead # of this hack here and below (i.e. mx.self_type). @@ -387,7 +384,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 +409,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 +430,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) @@ -629,7 +637,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 +697,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 +731,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,6 +752,16 @@ 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 @@ -879,6 +903,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 +931,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: @@ -1150,7 +1192,7 @@ class B(A[str]): pass t = freshen_all_functions_type_vars(t) t = bind_self(t, original_type, is_classmethod=True) assert isuper is not None - t = cast(CallableType, expand_type_by_instance(t, isuper)) + 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): diff --git a/mypy/checkpattern.py b/mypy/checkpattern.py index 603b392eee293..e432675b0b5a9 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, @@ -104,7 +104,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 +118,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) @@ -242,7 +247,7 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType: elif size_diff > 0 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) @@ -309,14 +314,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 +329,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 @@ -458,11 +463,11 @@ def visit_class_pattern(self, o: ClassPattern) -> PatternType: elif isinstance(type_info, TypeAlias): typ = type_info.target 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 +513,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 +583,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 e7602f33095d9..cda603be086b1 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 @@ -139,7 +139,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 @@ -435,9 +434,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, ) @@ -589,7 +588,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 @@ -844,10 +843,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 +949,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, ) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 190782a3bded7..47b0bc3acabc0 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] @@ -538,10 +539,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 a22c1b9ba9e58..4582b2a7396d7 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 a8f04094ca630..f9124630a706e 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -2,8 +2,7 @@ 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, cast import mypy.subtypes import mypy.typeops @@ -46,15 +45,11 @@ 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 +from mypy.typevartuples import extract_unpack, find_unpack_in_list, split_with_mapped_and_template if TYPE_CHECKING: from mypy.infer import ArgumentInferContext @@ -146,7 +141,7 @@ def infer_constraints_for_callable( # not to hold we can always handle the prefixes too. inner_unpack = unpacked_type.items[0] assert isinstance(inner_unpack, UnpackType) - inner_unpacked_type = get_proper_type(inner_unpack.type) + inner_unpacked_type = inner_unpack.type assert isinstance(inner_unpacked_type, TypeVarTupleType) suffix_len = len(unpacked_type.items) - 1 constraints.append( @@ -669,7 +664,7 @@ def visit_instance(self, template: Instance) -> list[Constraint]: instance.type.type_var_tuple_prefix, instance.type.type_var_tuple_suffix, ) - tvars = list(tvars_prefix + tvars_suffix) + tvars = cast("list[TypeVarLikeType]", list(tvars_prefix + tvars_suffix)) else: mapped_args = mapped.args instance_args = instance.args @@ -695,7 +690,7 @@ def visit_instance(self, template: Instance) -> list[Constraint]: 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): + if isinstance(suffix, (Parameters, CallableType)): # no such thing as variance for ParamSpecs # TODO: is there a case I am missing? # TODO: constraints between prefixes @@ -738,7 +733,7 @@ def visit_instance(self, template: Instance) -> list[Constraint]: template.type.type_var_tuple_prefix, template.type.type_var_tuple_suffix, ) - tvars = list(tvars_prefix + tvars_suffix) + tvars = cast("list[TypeVarLikeType]", list(tvars_prefix + tvars_suffix)) else: mapped_args = mapped.args template_args = template.args @@ -765,7 +760,7 @@ def visit_instance(self, template: Instance) -> list[Constraint]: 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): + if isinstance(suffix, (Parameters, CallableType)): # no such thing as variance for ParamSpecs # TODO: is there a case I am missing? # TODO: constraints between prefixes @@ -890,7 +885,30 @@ def visit_callable_type(self, template: CallableType) -> list[Constraint]: param_spec = template.param_spec() 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 cactual.param_spec() is None + # 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 actual is generic, unify it with template. Note: this is + # not an ideal solution (which would be adding the generic variables + # to the constraint inference set), but it's a good first approximation, + # and this will prevent leaking these variables in the solutions. + # Note: this may infer constraints like T <: S or T <: List[S] + # that contain variables in the target. + unified = mypy.subtypes.unify_generic_callable( + cactual, template, ignore_return=True + ) + if unified is not None: + cactual = unified + res.extend(infer_constraints(cactual, template, neg_op(self.direction))) # We can't infer constraints from arguments if the template is Callable[..., T] # (with literal '...'). @@ -949,7 +967,7 @@ def visit_callable_type(self, template: CallableType) -> list[Constraint]: ) # TODO: see above "FIX" comments for param_spec is None case - # TODO: this assume positional arguments + # TODO: this assumes positional arguments for t, a in zip(prefix.arg_types, cactual_prefix.arg_types): res.extend(infer_constraints(t, a, neg_op(self.direction))) @@ -1006,7 +1024,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 = ( @@ -1065,7 +1082,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): @@ -1159,7 +1176,7 @@ def find_matching_overload_items( 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 diff --git a/mypy/copytype.py b/mypy/copytype.py index 6024e527705bb..4ca381c4a8c40 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 02562b5f09637..2a881975a27ce 100644 --- a/mypy/defaults.py +++ b/mypy/defaults.py @@ -1,14 +1,14 @@ from __future__ import annotations import os -from typing_extensions import Final +from typing import Final PYTHON2_VERSION: Final = (2, 7) # 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. @@ -44,5 +44,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 efa1b5f012888..0e91206085096 100644 --- a/mypy/dmypy/client.py +++ b/mypy/dmypy/client.py @@ -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 --)" ) @@ -608,21 +609,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) @@ -665,10 +667,15 @@ def request( return {"error": str(err)} # TODO: Other errors, e.g. ValueError, UnicodeError else: - # Display debugging output written to stdout in the server process for convenience. + # Display debugging output written to stdout/stderr in the server process for convenience. stdout = response.get("stdout") if stdout: sys.stdout.write(stdout) + stderr = response.get("stderr") + if stderr: + print("-" * 79) + print("stderr:") + sys.stdout.write(stderr) return response diff --git a/mypy/dmypy_server.py b/mypy/dmypy_server.py index 7227cd5599468..a50ebc5415ba9 100644 --- a/mypy/dmypy_server.py +++ b/mypy/dmypy_server.py @@ -17,8 +17,8 @@ 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 @@ -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()). @@ -215,7 +218,9 @@ def serve(self) -> None: with server: data = receive(server) debug_stdout = io.StringIO() + debug_stderr = io.StringIO() sys.stdout = debug_stdout + sys.stderr = debug_stderr resp: dict[str, Any] = {} if "command" not in data: resp = {"error": "No command found in request"} @@ -233,9 +238,11 @@ def serve(self) -> None: resp = {"error": "Daemon crashed!\n" + "".join(tb)} resp.update(self._response_metadata()) resp["stdout"] = debug_stdout.getvalue() + resp["stderr"] = debug_stderr.getvalue() server.write(json.dumps(resp).encode("utf8")) raise resp["stdout"] = debug_stdout.getvalue() + resp["stderr"] = debug_stderr.getvalue() try: resp.update(self._response_metadata()) server.write(json.dumps(resp).encode("utf8")) @@ -323,7 +330,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"} @@ -596,7 +603,7 @@ def fine_grained_increment_follow_imports(self, sources: list[BuildSource]) -> l 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 +710,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 +831,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 +857,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 +896,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' diff --git a/mypy/dmypy_util.py b/mypy/dmypy_util.py index a1b419617f735..2aae41d998da4 100644 --- a/mypy/dmypy_util.py +++ b/mypy/dmypy_util.py @@ -6,8 +6,7 @@ from __future__ import annotations import json -from typing import Any -from typing_extensions import Final +from typing import Any, Final from mypy.ipc import IPCBase diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index 3d8b1096ed4f8..717629ad1f110 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") @@ -113,7 +124,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,6 +143,9 @@ 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" +) # These error codes aren't enabled by default. NO_UNTYPED_DEF: Final[ErrorCode] = ErrorCode( @@ -218,10 +232,19 @@ 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, +) # 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. diff --git a/mypy/errors.py b/mypy/errors.py index 2c2c1e5ca227a..2badac3e3d6d5 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -4,8 +4,8 @@ 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 @@ -20,8 +20,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 +109,7 @@ class ErrorInfo: def __init__( self, import_ctx: list[tuple[str, int]], + *, file: str, module: str | None, typ: str | None, @@ -106,6 +126,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 +145,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: @@ -222,6 +244,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 +284,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 +303,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 +324,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 +352,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 +368,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 +438,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(), ) @@ -515,32 +534,61 @@ 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 + ): + 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 +682,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 +757,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) @@ -802,9 +854,9 @@ def format_messages( ) in errors: 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 +874,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] @@ -853,7 +905,7 @@ def file_messages(self, path: str) -> list[str]: return [] self.flushed_files.add(path) source_lines = None - if self.pretty: + if self.options.pretty: assert self.read_source source_lines = self.read_source(path) return self.format_messages(self.error_info_map[path], source_lines) @@ -894,7 +946,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 +971,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 +1091,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 diff --git a/mypy/evalexpr.py b/mypy/evalexpr.py index 2bc6966fa2fac..4b3abb1be3e2b 100644 --- a/mypy/evalexpr.py +++ b/mypy/evalexpr.py @@ -7,7 +7,7 @@ """ 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 7933283b24d6a..83d9bf4c8725d 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.state import state from mypy.types import ( ANY_STRATEGY, AnyType, @@ -18,9 +17,11 @@ NoneType, Overloaded, Parameters, + ParamSpecFlavor, ParamSpecType, PartialType, ProperType, + TrivialSyntheticTypeTranslator, TupleType, Type, TypeAliasType, @@ -30,44 +31,50 @@ TypeVarLikeType, TypeVarTupleType, TypeVarType, - TypeVisitor, UnboundType, UninhabitedType, UnionType, UnpackType, - expand_param_spec, + flatten_nested_tuples, 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 find_unpack_in_list, 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: CallableType, env: Mapping[TypeVarId, Type]) -> CallableType: + ... @overload -def expand_type( - typ: ProperType, env: Mapping[TypeVarId, Type], allow_erased_callables: bool = ... -) -> ProperType: +def expand_type(typ: ProperType, env: Mapping[TypeVarId, Type]) -> ProperType: ... @overload -def expand_type( - typ: Type, env: Mapping[TypeVarId, Type], allow_erased_callables: bool = ... -) -> Type: +def expand_type(typ: Type, env: Mapping[TypeVarId, Type]) -> Type: ... -def expand_type( - typ: Type, env: Mapping[TypeVarId, Type], allow_erased_callables: bool = False -) -> 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: CallableType, instance: Instance) -> CallableType: + ... @overload @@ -107,6 +114,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 +131,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 +168,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 +179,13 @@ 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 def visit_unbound_type(self, t: UnboundType) -> Type: return t @@ -205,13 +203,12 @@ 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: @@ -234,13 +231,42 @@ def visit_type_var(self, t: TypeVarType) -> Type: return repl def visit_param_spec(self, t: ParamSpecType) -> Type: - repl = get_proper_type(self.variables.get(t.id, t)) + # set prefix to something empty so we don't duplicate it + repl = get_proper_type( + self.variables.get(t.id, t.copy_modified(prefix=Parameters([], [], []))) + ) 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) + 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], + ) + else: # TODO: should this branch be removed? better not to fail silently return repl @@ -249,12 +275,14 @@ def visit_type_var_tuple(self, t: TypeVarTupleType) -> Type: 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" + # However, if the item is a variadic tuple, we can simply carry it over. + # it is hard to assert this without getting proper type. + return UnpackType(t.type.accept(self)) def expand_unpack(self, t: UnpackType) -> list[Type] | Instance | AnyType | None: return expand_unpack_with_variables(t, self.variables) @@ -268,10 +296,11 @@ def interpolate_args_for_unpack( 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)) + var_arg_type = get_proper_type(var_arg.type) + if isinstance(var_arg_type, TupleType): + expanded_tuple = var_arg_type.accept(self) # TODO: handle the case that expanded_tuple is a variable length tuple. - assert isinstance(expanded_tuple, TupleType) + assert isinstance(expanded_tuple, ProperType) and isinstance(expanded_tuple, TupleType) expanded_items = expanded_tuple.items else: expanded_items_res = self.expand_unpack(var_arg) @@ -317,12 +346,20 @@ def interpolate_args_for_unpack( # 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]) + expanded_unpack = 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) + expanded_unpacked_tvt = expanded_unpack.type + if isinstance(expanded_unpacked_tvt, TypeVarTupleType): + fallback = expanded_unpacked_tvt.tuple_fallback + else: + # This can happen when tuple[Any, ...] is used to "patch" a variadic + # generic type without type arguments provided. + assert isinstance(expanded_unpacked_tvt, ProperType) + assert isinstance(expanded_unpacked_tvt, Instance) + assert expanded_unpacked_tvt.type.fullname == "builtins.tuple" + fallback = expanded_unpacked_tvt prefix_len = expanded_unpack_index arg_names = t.arg_names[:star_index] + [None] * prefix_len + t.arg_names[star_index:] @@ -334,11 +371,7 @@ def interpolate_args_for_unpack( + expanded_items[:prefix_len] # Constructing the Unpack containing the tuple without the prefix. + [ - UnpackType( - TupleType( - expanded_items[prefix_len:], expanded_unpacked_tvt.tuple_fallback - ) - ) + UnpackType(TupleType(expanded_items[prefix_len:], fallback)) if len(expanded_items) - prefix_len > 1 else expanded_items[0] ] @@ -346,7 +379,7 @@ def interpolate_args_for_unpack( ) return (arg_names, arg_kinds, arg_types) - 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)) @@ -357,7 +390,7 @@ 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): + if isinstance(repl, (CallableType, 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 @@ -370,6 +403,23 @@ def visit_callable_type(self, t: CallableType) -> Type: ret_type=t.ret_type.accept(self), type_guard=(t.type_guard.accept(self) if t.type_guard is not None else None), ) + # TODO: Conceptually, the "len(t.arg_types) == 2" should not be here. However, this + # errors without it. Either figure out how to eliminate this or place an + # explanation for why this is necessary. + elif isinstance(repl, ParamSpecType) and len(t.arg_types) == 2: + # We're substituting one paramspec for another; this can mean that the prefix + # changes. (e.g. sub Concatenate[int, P] for Q) + prefix = repl.prefix + old_prefix = param_spec.prefix + + # Check assumptions. I'm not sure what order to place new prefix vs old prefix: + assert not old_prefix.arg_types or not prefix.arg_types + + t = t.copy_modified( + arg_types=prefix.arg_types + old_prefix.arg_types + t.arg_types, + arg_kinds=prefix.arg_kinds + old_prefix.arg_kinds + t.arg_kinds, + arg_names=prefix.arg_names + old_prefix.arg_names + t.arg_names, + ) var_arg = t.var_arg() if var_arg is not None and isinstance(var_arg.typ, UnpackType): @@ -405,9 +455,12 @@ def expand_types_with_unpack( indicates use of Any or some error occurred earlier. In this case callers should simply propagate the resulting type. """ + # TODO: this will cause a crash on aliases like A = Tuple[int, Unpack[A]]. + # Although it is unlikely anyone will write this, we should fail gracefully. + typs = flatten_nested_tuples(typs) items: list[Type] = [] for item in typs: - if isinstance(item, UnpackType): + if isinstance(item, UnpackType) and isinstance(item.type, TypeVarTupleType): unpacked_items = self.expand_unpack(item) if unpacked_items is None: # TODO: better error, something like tuple of unknown? @@ -430,18 +483,14 @@ 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 + assert isinstance(fallback, ProperType) and isinstance(fallback, Instance) return t.copy_modified(items=items, fallback=fallback) else: return items 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 +502,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 +525,11 @@ 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) + if isinstance(args, list): + return t.copy_modified(args=args) + else: + return args def expand_types(self, types: Iterable[Type]) -> list[Type]: a: list[Type] = [] @@ -522,3 +581,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/fastparse.py b/mypy/fastparse.py index ef1fdf61af2e0..f7a98e9b2b8fb 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, @@ -99,6 +100,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, @@ -116,123 +118,57 @@ ) 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, 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 N = TypeVar("N", bound=Node) @@ -241,10 +177,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*(.*)") @@ -255,17 +187,21 @@ def parse( errors: Errors | None = None, 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. """ + ignore_errors = (options is not None and options.ignore_errors) or ( + errors is not None and 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) raise_on_error = False if options is None: options = Options() if errors is None: - errors = Errors(hide_error_codes=options.hide_error_codes) + errors = Errors(options) raise_on_error = True errors.set_file(fnam, module, options=options) is_stub_file = fnam.endswith(".pyi") @@ -282,7 +218,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 = ASTConverter( + options=options, + is_stub=is_stub_file, + errors=errors, + ignore_errors=ignore_errors, + strip_function_bodies=strip_function_bodies, + ).visit(ast) tree.path = fnam tree.is_stub = is_stub_file except SyntaxError as e: @@ -343,25 +285,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) @@ -402,14 +345,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, + *, + ignore_errors: bool, + strip_function_bodies: bool, + ) -> 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.ignore_errors = ignore_errors + self.strip_function_bodies = strip_function_bodies self.type_ignores: dict[int, list[str]] = {} @@ -419,24 +372,16 @@ 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) + 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 +422,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 +437,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 +450,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 +554,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]: @@ -664,7 +671,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 +838,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,11 +847,11 @@ 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) @@ -866,7 +872,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) ) @@ -889,8 +895,10 @@ 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 @@ -913,11 +921,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( @@ -937,18 +946,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 +974,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) 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 +988,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 +999,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] @@ -1091,7 +1096,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, @@ -1104,7 +1109,7 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef: 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"), @@ -1113,12 +1118,9 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef: 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 + cdef.column = n.col_offset cdef.end_line = getattr(n, "end_lineno", None) cdef.end_column = getattr(n, "end_col_offset", None) @@ -1176,8 +1178,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 +1190,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 +1200,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 +1217,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 +1228,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 +1245,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 +1262,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 +1404,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 +1507,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 +1524,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 +1535,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 +1554,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 +1565,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 +1646,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 +1670,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) @@ -1816,9 +1783,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 +1805,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 +1818,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 +1832,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 +1841,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 +1851,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 +1879,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 +1886,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) @@ -1972,23 +1930,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 +1958,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,10 +2002,6 @@ 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) - # List(expr* elts, expr_context ctx) def visit_List(self, n: ast3.List) -> Type: assert isinstance(n.ctx, ast3.Load) @@ -2079,3 +2016,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 a3ef2d3db052f..3565fc4609cd9 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 7b0f5f433d72e..2b2e1210ee4e1 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 ( @@ -170,18 +169,22 @@ def visit_class_def(self, c: ClassDef) -> None: if isinstance(v, TypeVarType): for value in v.values: value.accept(self.type_fixer) - v.upper_bound.accept(self.type_fixer) + v.upper_bound.accept(self.type_fixer) + v.default.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.default.accept(self.type_fixer) def visit_var(self, v: Var) -> None: if self.current_info is not None: @@ -303,14 +306,16 @@ 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.upper_bound.accept(self) + t.default.accept(self) def visit_unpack_type(self, u: UnpackType) -> None: u.type.accept(self) diff --git a/mypy/graph_utils.py b/mypy/graph_utils.py new file mode 100644 index 0000000000000..399301a6b0fdd --- /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 eef7601d6aae7..00356d7a4ddbe 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 fbec3d7c4278b..66ca4169e2ff5 100644 --- a/mypy/infer.py +++ b/mypy/infer.py @@ -36,6 +36,7 @@ def infer_function_type_arguments( formal_to_actual: list[list[int]], context: ArgumentInferContext, strict: bool = True, + allow_polymorphic: bool = False, ) -> list[Type | None]: """Infer the type arguments of a generic function. @@ -57,7 +58,7 @@ def infer_function_type_arguments( # Solve constraints. type_vars = callee_type.type_var_ids() - return solve_constraints(type_vars, constraints, strict) + return solve_constraints(type_vars, constraints, strict, allow_polymorphic) def infer_type_arguments( diff --git a/mypy/inspections.py b/mypy/inspections.py index d99e087b93a16..cb695a80eef29 100644 --- a/mypy/inspections.py +++ b/mypy/inspections.py @@ -247,7 +247,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: diff --git a/mypy/ipc.py b/mypy/ipc.py index f07616df0fd0a..d026f2429a0f2 100644 --- a/mypy/ipc.py +++ b/mypy/ipc.py @@ -12,8 +12,7 @@ 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 @@ -169,7 +168,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 62d256f4440f9..f4af59f4e50b6 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -29,7 +29,6 @@ Parameters, ParamSpecType, PartialType, - PlaceholderType, ProperType, TupleType, Type, @@ -246,14 +245,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) diff --git a/mypy/literals.py b/mypy/literals.py index 9d91cf728b066..cba5712644be2 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 47dea2ae9797a..f6e617e4d84f0 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -8,8 +8,7 @@ import sys import time 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 @@ -696,7 +695,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 +734,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 +825,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 +886,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, @@ -974,6 +990,11 @@ def add_invertible_flag( dest="custom_typing_module", help="Use a custom typing module", ) + internals_group.add_argument( + "--new-type-inference", + action="store_true", + help="Enable new experimental type inference algorithm", + ) internals_group.add_argument( "--disable-recursive-aliases", action="store_true", @@ -1017,6 +1038,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." @@ -1139,6 +1162,8 @@ def add_invertible_flag( 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 +1235,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) @@ -1320,12 +1348,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 # type: ignore[assignment] # Set reports. for flag, val in vars(special_opts).items(): @@ -1363,6 +1391,8 @@ def set_strict_flags() -> None: "Warning: --enable-recursive-aliases is deprecated;" " recursive types are 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: @@ -1488,7 +1518,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/meet.py b/mypy/meet.py index d99e1a92d2eb0..29c4d3663503c 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -342,7 +342,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 @@ -687,7 +702,7 @@ def visit_unpack_type(self, t: UnpackType) -> ProperType: 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, CallableType)): if len(t.arg_types) != len(self.s.arg_types): return self.default(self.s) return t.copy_modified( @@ -813,13 +828,13 @@ def visit_tuple_type(self, t: TupleType) -> ProperType: 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: diff --git a/mypy/memprofile.py b/mypy/memprofile.py index 20e18c3c0bf23..48c0cb5ce0228 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 e00aca2869bdb..bd3b8571b69e9 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" ) @@ -82,7 +83,7 @@ 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") +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 +132,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( @@ -179,7 +180,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 " @@ -268,9 +269,50 @@ 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 +) diff --git a/mypy/messages.py b/mypy/messages.py index ba25080337904..ae7fba1473ac0 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) @@ -366,7 +379,7 @@ def has_no_attr( self.fail(f'Member "{member}" is not assignable', context) 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, ) @@ -379,19 +392,19 @@ def has_no_attr( break 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, ) 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, ) 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, ) @@ -401,13 +414,13 @@ def has_no_attr( if isinstance(original_type, CallableType) 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, ) 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, ) @@ -415,7 +428,7 @@ def has_no_attr( # Indexed set. self.fail( "Unsupported target for indexed assignment ({})".format( - format_type(original_type) + format_type(original_type, self.options) ), context, code=codes.INDEX, @@ -429,7 +442,7 @@ def has_no_attr( self.fail("Cannot call function of unknown type", context, code=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, ) @@ -449,7 +462,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 +484,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,7 +496,7 @@ 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, @@ -491,7 +504,9 @@ def has_no_attr( 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 ): @@ -506,8 +521,8 @@ def has_no_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,6 +531,14 @@ def has_no_attr( context, code=codes.UNION_ATTR, ) + else: + self.fail( + '{} has no attribute "{}"{}'.format( + format_type(original_type, self.options), member, extra + ), + context, + code=codes.ATTR_DEFINED, + ) return AnyType(TypeOfAny.from_error) def unsupported_operand_types( @@ -535,13 +558,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 +576,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 +620,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 +653,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 +674,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 +713,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 +747,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 +761,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 +785,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 +811,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 +896,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 @@ -1030,7 +1068,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 +1091,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,7 +1112,7 @@ 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) @@ -1099,13 +1137,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 +1158,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 +1170,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 +1191,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 +1216,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 +1244,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 +1254,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 +1299,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 @@ -1268,6 +1336,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 +1354,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 +1375,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, @@ -1373,7 +1447,7 @@ def cannot_determine_type_in_base(self, name: str, base: str, context: Context) def no_formal_self(self, name: str, item: CallableType, context: Context) -> None: self.fail( 'Attribute function "%s" with type %s does not accept self argument' - % (name, format_type(item)), + % (name, format_type(item, self.options)), context, ) @@ -1383,7 +1457,7 @@ def incompatible_self_argument( kind = "class attribute function" if is_classmethod else "attribute function" self.fail( 'Invalid self argument %s to %s "%s" with type %s' - % (format_type(arg), kind, name, format_type(sig)), + % (format_type(arg, self.options), kind, name, format_type(sig, self.options)), context, ) @@ -1444,6 +1518,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 +1567,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 +1577,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, @@ -1544,7 +1635,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 +1651,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 +1666,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 +1696,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, ) @@ -1661,6 +1768,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 +1802,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 +1811,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 +1857,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 +1868,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 +1880,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 +1889,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 +1902,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 +1935,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 +1955,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 +1988,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, @@ -1999,7 +2131,7 @@ def report_protocol_problems( or not subtype.type.defn.type_vars or not supertype.type.defn.type_vars ): - 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 +2140,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 +2151,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 +2164,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 +2249,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 +2322,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 +2340,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 @@ -2269,7 +2408,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 +2420,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 +2435,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 +2469,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,7 +2478,10 @@ 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 @@ -2339,11 +2490,7 @@ def format_literal_value(typ: LiteralType) -> str: 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)}]" @@ -2369,14 +2516,17 @@ 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)}]" + if options.use_lowercase_names(): + s = f"tuple[{format_list(typ.items)}]" + else: + s = f"Tuple[{format_list(typ.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 +2544,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 +2565,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" @@ -2424,7 +2589,8 @@ def format_literal_value(typ: LiteralType) -> str: else: return "" 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(): @@ -2451,7 +2617,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 +2674,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 +2687,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 +2704,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 +2722,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 +2743,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 +2769,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 @@ -2640,9 +2811,9 @@ 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)}]" else: - s += format_type_bare(tp.ret_type) + s += format_type_bare(tp.ret_type, options) if tp.variables: tvars = [] @@ -2653,11 +2824,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: @@ -2705,7 +2877,7 @@ 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) @@ -2775,7 +2947,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: @@ -2849,8 +3021,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] @@ -2902,6 +3075,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/modulefinder.py b/mypy/modulefinder.py index 265d76ed5bb64..c780015c639dc 100644 --- a/mypy/modulefinder.py +++ b/mypy/modulefinder.py @@ -21,8 +21,8 @@ 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.fscache import FileSystemCache @@ -751,12 +751,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( diff --git a/mypy/mro.py b/mypy/mro.py index cc9f88a9d0455..f34f3fa0c46d7 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 4787930214f37..2d763fc482d3e 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -11,6 +11,7 @@ Any, Callable, Dict, + Final, Iterator, List, Optional, @@ -20,11 +21,12 @@ 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.options import Options from mypy.util import short_type from mypy.visitor import ExpressionVisitor, NodeVisitor, StatementVisitor @@ -173,7 +175,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 {} @@ -190,11 +192,16 @@ 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") @@ -281,6 +288,7 @@ class MypyFile(SymbolNode): "names", "imports", "ignored_lines", + "skipped_lines", "is_stub", "is_cache_skeleton", "is_partial_stub_package", @@ -307,6 +315,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? @@ -339,6 +350,7 @@ def __init__( self.ignored_lines = ignored_lines else: self.ignored_lines = {} + self.skipped_lines = set() self.path = "" self.is_stub = False @@ -500,8 +512,9 @@ 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" "_fullname", ) @@ -519,6 +532,7 @@ def __init__(self) -> None: self.is_class = False self.is_static = False self.is_final = False + self.is_explicit_override = False # Name with module prefix self._fullname = "" @@ -556,7 +570,7 @@ 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 @@ -901,6 +915,7 @@ def deserialize(cls, data: JsonDict) -> Decorator: VAR_FLAGS: Final = [ "is_self", + "is_cls", "is_initialized_in_class", "is_staticmethod", "is_classmethod", @@ -935,6 +950,7 @@ class Var(SymbolNode): "type", "final_value", "is_self", + "is_cls", "is_ready", "is_inferred", "is_initialized_in_class", @@ -967,6 +983,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? @@ -985,7 +1003,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 @@ -1631,6 +1649,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""" @@ -2175,7 +2197,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 @@ -2418,13 +2441,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") _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 @@ -2432,12 +2458,18 @@ 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, ) -> None: super().__init__() self._name = name self._fullname = fullname self.upper_bound = upper_bound + self.default = default self.variance = variance @property @@ -2463,7 +2495,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. @@ -2475,9 +2507,10 @@ def __init__( fullname: str, values: list[mypy.types.Type], upper_bound: mypy.types.Type, + default: mypy.types.Type, variance: int = INVARIANT, ) -> None: - super().__init__(name, fullname, upper_bound, variance) + super().__init__(name, fullname, upper_bound, default, variance) self.values = values def accept(self, visitor: ExpressionVisitor[T]) -> T: @@ -2490,6 +2523,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, } @@ -2501,6 +2535,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"], ) @@ -2508,7 +2543,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) @@ -2519,6 +2554,7 @@ def serialize(self) -> JsonDict: "name": self._name, "fullname": self._fullname, "upper_bound": self.upper_bound.serialize(), + "default": self.default.serialize(), "variance": self.variance, } @@ -2529,6 +2565,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"], ) @@ -2540,7 +2577,7 @@ class TypeVarTupleExpr(TypeVarLikeExpr): tuple_fallback: mypy.types.Instance - __match_args__ = ("name", "upper_bound") + __match_args__ = ("name", "upper_bound", "default") def __init__( self, @@ -2548,9 +2585,10 @@ def __init__( fullname: str, upper_bound: mypy.types.Type, tuple_fallback: mypy.types.Instance, + default: mypy.types.Type, variance: int = INVARIANT, ) -> None: - super().__init__(name, fullname, upper_bound, variance) + super().__init__(name, fullname, upper_bound, default, variance) self.tuple_fallback = tuple_fallback def accept(self, visitor: ExpressionVisitor[T]) -> T: @@ -2563,6 +2601,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, } @@ -2574,6 +2613,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"], ) @@ -2763,6 +2803,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. @@ -3074,9 +3133,11 @@ 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)) @@ -3174,22 +3235,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: @@ -3317,7 +3377,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. @@ -3452,6 +3511,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") @@ -3479,6 +3539,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 @@ -3906,8 +3970,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 @@ -3917,7 +3981,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 2b383ef199bb0..07ec5a24fa77c 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 = { diff --git a/mypy/options.py b/mypy/options.py index 92c96a92c531f..75343acd38bb1 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", @@ -86,7 +87,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. @@ -191,9 +200,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 @@ -300,6 +312,7 @@ def __init__(self) -> 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 +348,38 @@ 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 + # Enable new experimental type inference algorithm. + self.new_type_inference = False # Disable recursive type aliases (currently experimental) self.disable_recursive_aliases = False # Deprecated reverse version of the above, do not use. self.enable_recursive_aliases = 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 + + 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 + # 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 +394,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 +420,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 +528,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/partially_defined.py b/mypy/partially_defined.py index 9b8238eff83f7..47cbd671f1682 100644 --- a/mypy/partially_defined.py +++ b/mypy/partially_defined.py @@ -79,7 +79,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 +153,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 +173,7 @@ class ScopeType(Enum): Global = 1 Class = 2 Func = 3 - Generator = 3 + Generator = 4 class Scope: @@ -199,7 +201,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 +218,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 +346,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 +421,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) @@ -603,7 +616,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 +660,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]) diff --git a/mypy/plugin.py b/mypy/plugin.py index cf124b45d04fd..4d62c2bd184b8 100644 --- a/mypy/plugin.py +++ b/mypy/plugin.py @@ -250,6 +250,11 @@ def named_generic_type(self, name: str, args: list[Type]) -> Instance: """Construct an instance of a builtin 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 + @trait class SemanticAnalyzerPluginInterface: diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 6fda965ade8ba..ec6dc71c56915 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, @@ -47,7 +55,7 @@ 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,12 @@ LiteralType, NoneType, Overloaded, + ProperType, TupleType, Type, TypeOfAny, TypeVarType, + UninhabitedType, UnionType, get_proper_type, ) @@ -77,13 +87,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: @@ -112,11 +124,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) @@ -272,6 +293,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 +304,7 @@ 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. + 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 +314,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,7 +352,7 @@ 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: @@ -511,7 +533,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 @@ -650,6 +671,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 +709,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 +766,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) @@ -843,6 +895,13 @@ def _add_slots(ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute]) -> # 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 ( @@ -888,3 +947,126 @@ def add_method( """ 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) + + +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}", + ) diff --git a/mypy/plugins/common.py b/mypy/plugins/common.py index 0acf3e3a63695..65d967577beac 100644 --- a/mypy/plugins/common.py +++ b/mypy/plugins/common.py @@ -1,5 +1,6 @@ from __future__ import annotations +from mypy.argmap import map_actuals_to_formals from mypy.fixup import TypeFixer from mypy.nodes import ( ARG_POS, @@ -13,6 +14,7 @@ Expression, FuncDef, JsonDict, + NameExpr, Node, PassStmt, RefExpr, @@ -22,21 +24,26 @@ 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_optional from mypy.typevars import fill_typevars from mypy.util import get_unique_redefinition_name @@ -87,6 +94,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_optional(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 type(arg_type.value) is 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 +175,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 diff --git a/mypy/plugins/ctypes.py b/mypy/plugins/ctypes.py index edfbe506fccac..b6dbec13ce908 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 6b1062d6457f3..a4babe7faf614 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,15 @@ MDEF, Argument, AssignmentStmt, + Block, CallExpr, ClassDef, Context, DataclassTransformSpec, Expression, + FuncDef, + FuncItem, + IfStmt, JsonDict, NameExpr, Node, @@ -35,8 +40,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 +51,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 +102,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,18 +112,39 @@ 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), @@ -114,13 +152,16 @@ def to_argument(self, current_info: TypeInfo) -> Argument: 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, # 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 +179,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 +187,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 +254,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. @@ -248,7 +287,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 +305,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 +338,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"]: @@ -325,6 +377,11 @@ def transform(self) -> bool: self._add_dataclass_fields_magic_attribute() + if self._spec is _TRANSFORM_SPEC_FOR_DATACLASSES: + 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], "frozen": decorator_arguments["frozen"], @@ -332,6 +389,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: @@ -360,9 +444,15 @@ def add_slots( self._cls, ) 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 +470,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 +530,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 +543,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 @@ -491,7 +596,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 +615,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 +661,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 +671,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 +720,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 heirarchy 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 +769,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 +841,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 +958,159 @@ 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 _fail_not_dataclass(ctx: 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 "replace" has a variable type "{t_name}" not bound to a dataclass' + if isinstance(t, TypeVarType) + else f'Argument 1 to "replace" has incompatible type "{t_name}"; expected a dataclass' + ) + else: + pt_name = format_type_bare(parent_t, ctx.api.options) + msg = ( + f'Argument 1 to "replace" has type "{pt_name}" whose item "{t_name}" is not bound to a dataclass' + if isinstance(t, TypeVarType) + else f'Argument 1 to "replace" has incompatible type "{pt_name}" whose item "{t_name}" is not a dataclass' + ) + + ctx.api.fail(msg, ctx.context) + + +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, AnyType): + return None + elif 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: + _fail_not_dataclass(ctx, display_typ, parent_typ) + 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: + _fail_not_dataclass(ctx, display_typ, parent_typ) + 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 | None) -> bool: + return info is not None 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 4d6f468609390..b41ad98b13a67 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,22 @@ 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 == "dataclasses.replace": + return dataclasses.replace_function_sig_callback return None def get_method_signature_hook( @@ -59,7 +74,7 @@ def get_method_signature_hook( 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 @@ -82,9 +97,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 +110,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 +157,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 @@ -389,6 +406,31 @@ def typed_dict_update_signature_callback(ctx: MethodSigContext) -> CallableType: assert isinstance(arg_type, TypedDictType) 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 diff --git a/mypy/plugins/enums.py b/mypy/plugins/enums.py index 1acf42d11ee6d..7869a8b5cdfab 100644 --- a/mypy/plugins/enums.py +++ b/mypy/plugins/enums.py @@ -12,8 +12,7 @@ """ 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 diff --git a/mypy/plugins/functools.py b/mypy/plugins/functools.py index eba4d77f2343b..0aa2824c9b510 100644 --- a/mypy/plugins/functools.py +++ b/mypy/plugins/functools.py @@ -1,8 +1,7 @@ """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/mypy/plugins/singledispatch.py b/mypy/plugins/singledispatch.py index cd6a3a9fa1cc2..c5ce20233a0a4 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 5929bfb696b5d..f262ac8b2132c 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 8602fc645e2b2..a25b9dff45813 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 0000000000000..a5b92832bb7ee --- /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 05b67f41ab859..c960eb4b1ce82 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) diff --git a/mypy/report.py b/mypy/report.py index 75c372200ca37..5d93351aa37da 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 @@ -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] = {} @@ -704,8 +704,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): @@ -859,7 +860,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 19a690df82204..021dd9a7d8a5b 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 d2fd924996790..f4f281e7a77ab 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -51,8 +51,8 @@ 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 from mypy import errorcodes as codes, message_registry from mypy.constant_fold import constant_fold_expr @@ -216,6 +216,7 @@ calculate_tuple_fallback, find_dataclass_transform_spec, has_placeholder, + parse_bool, require_bool_literal_argument, set_callable_name as set_callable_name, ) @@ -233,7 +234,6 @@ fix_instance_types, has_any_from_unimported_type, no_subscript_builtin_alias, - remove_dups, type_constructors, ) from mypy.typeops import function_type, get_type_vars, try_getting_str_literals_from_type @@ -244,6 +244,7 @@ FINAL_TYPE_NAMES, NEVER_NAMES, OVERLOAD_NAMES, + OVERRIDE_DECORATOR_NAMES, PROTOCOL_NAMES, REVEAL_TYPE_NAMES, TPDICT_NAMES, @@ -268,15 +269,16 @@ TypeOfAny, TypeType, TypeVarLikeType, + TypeVarTupleType, TypeVarType, UnboundType, UnpackType, get_proper_type, get_proper_types, - invalid_recursive_alias, is_named_instance, - store_argument_type, + remove_dups, ) +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, @@ -373,7 +375,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? @@ -426,7 +428,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) @@ -509,6 +511,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 @@ -715,7 +718,7 @@ 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_types(target, self.fail, self.note, self.options) alias_node = TypeAlias( target, alias, @@ -867,11 +870,8 @@ def analyze_func_def(self, defn: FuncDef) -> None: 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, @@ -937,7 +937,7 @@ def analyze_func_def(self, defn: FuncDef) -> None: 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) @@ -959,9 +959,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: @@ -1008,7 +1010,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): @@ -1075,7 +1091,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 @@ -1198,6 +1221,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 @@ -1314,7 +1340,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): @@ -1326,6 +1353,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 @@ -1357,7 +1386,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) @@ -1369,8 +1399,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() @@ -1492,6 +1525,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, ( @@ -1544,6 +1581,8 @@ def visit_decorator(self, dec: Decorator) -> None: 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(): @@ -1591,6 +1630,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) @@ -1664,6 +1708,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() @@ -1716,6 +1762,10 @@ 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) + if isinstance(deco, RefExpr) and deco.fullname in FINAL_DECORATOR_NAMES: + info.is_final = True with self.named_tuple_analyzer.save_namedtuple_body(info): self.analyze_class_body_common(defn) return True @@ -1782,12 +1832,14 @@ def enter_class(self, info: TypeInfo) -> None: self.locals.append(None) # Add class scope self.is_comprehension_stack.append(False) 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._type = self.type_stack.pop() @@ -2163,6 +2215,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: @@ -2192,6 +2248,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: @@ -2272,12 +2333,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. @@ -2613,11 +2676,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( @@ -3025,6 +3091,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)): @@ -3033,9 +3105,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( @@ -3179,7 +3248,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) @@ -3347,7 +3416,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): @@ -3408,8 +3477,18 @@ def analyze_alias( allowed_alias_tvars=tvar_defs, ) + # 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 found_type_vars] - return analyzed, tvar_defs, depends_on, qualified_tvars + return analyzed, new_tvar_defs, depends_on, qualified_tvars def is_pep_613(self, s: AssignmentStmt) -> bool: if s.unanalyzed_type is not None and isinstance(s.unanalyzed_type, UnboundType): @@ -3528,7 +3607,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: # if the expected number of arguments is non-zero, so that aliases like A = List work. # 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) + fix_instance_types(res, self.fail, self.note, 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. @@ -3596,7 +3675,7 @@ def disable_invalid_recursive_aliases( ) -> None: """Prohibit and fix recursive type aliases that are invalid/unsupported.""" messages = [] - if invalid_recursive_alias({current_node}, current_node.target): + if is_invalid_recursive_alias({current_node}, current_node.target): messages.append("Invalid recursive alias: a union item of itself") if detect_diverging_alias( current_node, current_node.target, self.lookup_qualified, self.tvar_scope @@ -3638,7 +3717,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): @@ -3815,7 +3894,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: @@ -3844,12 +3925,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)) ): @@ -3944,7 +4031,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 ( @@ -3966,7 +4053,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 ) @@ -3979,19 +4066,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 @@ -4040,11 +4136,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) @@ -4067,28 +4164,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) @@ -4114,7 +4198,53 @@ 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, + 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, + ) + 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: @@ -4148,11 +4278,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 @@ -4160,12 +4329,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 @@ -4180,8 +4355,32 @@ 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) + + 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, + ) + 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) if not self.incomplete_feature_enabled(TYPE_VAR_TUPLE, s): return False @@ -4194,12 +4393,23 @@ 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), + self.object_type(), + 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 @@ -4416,7 +4626,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, @@ -4518,9 +4727,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: @@ -4542,20 +4751,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: @@ -4875,7 +5084,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]) @@ -4885,20 +5094,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) ] ) @@ -5105,15 +5320,10 @@ def analyze_type_application_args(self, expr: IndexExpr) -> list[Type] | 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))] @@ -5249,7 +5459,9 @@ 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) expr.expr.accept(self) @@ -5805,7 +6017,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`. @@ -6047,6 +6259,7 @@ def enter( 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 @@ -6056,6 +6269,7 @@ def enter( 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: @@ -6268,6 +6482,8 @@ 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, ) -> Type | None: if isinstance(expr, CallExpr): # This is a legacy syntax intended mostly for Python 2, we keep it for @@ -6296,6 +6512,8 @@ 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, ) def analyze_type_expr(self, expr: Expression) -> None: @@ -6455,12 +6673,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 @@ -6680,7 +6894,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: @@ -6690,11 +6904,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 3f5bc9c4c2de2..dfd4e5b6f1224 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 ( diff --git a/mypy/semanal_enum.py b/mypy/semanal_enum.py index c7b8e44f65aa6..cd11204c3bcc6 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 @@ -177,8 +177,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,7 +231,7 @@ 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) diff --git a/mypy/semanal_main.py b/mypy/semanal_main.py index a5e85878e931f..51a7014fac1ab 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 diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index 1194557836b12..42f7b10f3333d 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -6,8 +6,7 @@ from __future__ import annotations from contextlib import contextmanager -from typing import Iterator, List, Mapping, cast -from typing_extensions import Final +from typing import Final, Iterator, List, Mapping, cast from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type from mypy.nodes import ( @@ -41,6 +40,7 @@ TypeInfo, TypeVarExpr, Var, + is_StrExpr_list, ) from mypy.options import Options from mypy.semanal_shared import ( @@ -280,7 +280,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) } @@ -373,7 +373,7 @@ def parse_namedtuple_args( 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 +392,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] @@ -543,11 +543,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 @@ -603,20 +604,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 diff --git a/mypy/semanal_newtype.py b/mypy/semanal_newtype.py index cb1055a621862..a8380309d3109 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") diff --git a/mypy/semanal_pass1.py b/mypy/semanal_pass1.py index 55430be00a1e2..2df06feacca8f 100644 --- a/mypy/semanal_pass1.py +++ b/mypy/semanal_pass1.py @@ -62,6 +62,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 +70,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 +125,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 03efbe6ca1b8d..425e5906926a6 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,6 +42,7 @@ ProperType, TupleType, Type, + TypeOfAny, TypeVarId, TypeVarLikeType, get_proper_type, @@ -307,6 +310,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 +335,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, @@ -451,7 +456,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 +464,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 b9965236c3790..e188955dabbb2 100644 --- a/mypy/semanal_typeargs.py +++ b/mypy/semanal_typeargs.py @@ -18,6 +18,7 @@ from mypy.options import Options from mypy.scope import Scope from mypy.subtypes import is_same_type, is_subtype +from mypy.typeanal import set_any_tvars from mypy.types import ( AnyType, Instance, @@ -32,8 +33,10 @@ TypeVarType, UnboundType, UnpackType, + flatten_nested_tuples, get_proper_type, get_proper_types, + split_with_prefix_and_suffix, ) @@ -79,10 +82,34 @@ def visit_type_alias_type(self, t: TypeAliasType) -> None: 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] + # correct aliases. Also, variadic aliases are better to check when fully analyzed, + # so we do this here. assert t.alias is not None, f"Unfixed type alias {t.type_ref}" + args = flatten_nested_tuples(t.args) + if t.alias.tvar_tuple_index is not None: + correct = len(args) >= len(t.alias.alias_tvars) - 1 + if any( + isinstance(a, UnpackType) and isinstance(get_proper_type(a.type), Instance) + for a in args + ): + correct = True + else: + correct = len(args) == len(t.alias.alias_tvars) + if not correct: + if t.alias.tvar_tuple_index is not None: + exp_len = f"at least {len(t.alias.alias_tvars) - 1}" + else: + exp_len = f"{len(t.alias.alias_tvars)}" + self.fail( + f"Bad number of arguments for type alias, expected: {exp_len}, given: {len(args)}", + t, + code=codes.TYPE_ARG, + ) + t.args = set_any_tvars( + t.alias, t.line, t.column, self.options, from_error=True, fail=self.fail + ).args + else: + t.args = args is_error = self.validate_args(t.alias.name, 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 @@ -101,6 +128,17 @@ def visit_instance(self, t: Instance) -> None: def validate_args( self, name: str, args: Sequence[Type], type_vars: list[TypeVarLikeType], ctx: Context ) -> bool: + # TODO: we need to do flatten_nested_tuples and validate arg count for instances + # similar to how do we do this for type aliases above, but this may have perf penalty. + 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 = list(start) + [TupleType(list(middle), tvt.tuple_fallback)] + list(end) + is_error = False for (i, arg), tvar in zip(enumerate(args), type_vars): if isinstance(tvar, TypeVarType): @@ -141,7 +179,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(tvar.upper_bound, self.options), ), ctx, code=codes.TYPE_VAR, @@ -152,7 +192,7 @@ 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 @@ -165,12 +205,18 @@ def visit_unpack_type(self, typ: UnpackType) -> None: return 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: + if ( + isinstance(proper_type, UnboundType) + or 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) + self.fail( + message_registry.INVALID_UNPACK.format(format_type(proper_type, self.options)), typ + ) 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 acb93edb7d2dc..aba5bf69b1303 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 ( @@ -45,7 +46,6 @@ TypedDictType, TypeOfAny, TypeVarLikeType, - replace_alias_tvars, ) TPDICT_CLASS_ERROR: Final = ( @@ -243,10 +243,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 +252,9 @@ 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 + 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( @@ -469,7 +466,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) diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index 40b60f1a69d80..93f178dca35a2 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,7 @@ 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.types import ( AnyType, CallableType, @@ -189,6 +190,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 +201,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 +242,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 +252,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 +270,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 +298,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 +393,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 +404,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 +413,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 +456,7 @@ 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) + 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 1ec6d572a82c7..5e3759227c7b3 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -250,6 +250,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) @@ -358,7 +367,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) @@ -477,14 +487,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) diff --git a/mypy/server/deps.py b/mypy/server/deps.py index 50b66b70b8aaf..ed85b74f22061 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -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 ef6f5b86c8f3c..6f044a5ea8b94 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 89a086b8a0ab2..a13fd84129340 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 5f2115739d382..97b5f89cd3ba5 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 00b823c99dfd0..0cc7a2229514d 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 6f864ccce816d..ef2e4f7206649 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 b8304d29c1ce9..6693d66f3479d 100644 --- a/mypy/solve.py +++ b/mypy/solve.py @@ -2,27 +2,35 @@ from __future__ import annotations -from collections import defaultdict +from typing import Iterable -from mypy.constraints import SUPERTYPE_OF, Constraint +from mypy.constraints import SUBTYPE_OF, SUPERTYPE_OF, Constraint, 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.subtypes import is_subtype +from mypy.typeops import get_type_vars from mypy.types import ( AnyType, ProperType, Type, TypeOfAny, TypeVarId, + TypeVarType, UninhabitedType, UnionType, get_proper_type, + remove_dups, ) from mypy.typestate import type_state def solve_constraints( - vars: list[TypeVarId], constraints: list[Constraint], strict: bool = True + vars: list[TypeVarId], + constraints: list[Constraint], + strict: bool = True, + allow_polymorphic: bool = False, ) -> list[Type | None]: """Solve type constraints. @@ -33,62 +41,355 @@ def solve_constraints( pick NoneType as the value of the type variable. If strict=False, pick AnyType. """ + if not vars: + return [] + if allow_polymorphic: + # Constraints like T :> S and S <: T are semantically the same, but they are + # represented differently. Normalize the constraint list w.r.t this equivalence. + constraints = normalize_constraints(constraints, vars) + # 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} for con in constraints: - cmap[con.type_var].append(con) + if con.type_var in vars: + cmap[con.type_var].append(con) + + if allow_polymorphic: + solutions = solve_non_linear(vars, constraints, cmap) + else: + solutions = {} + 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] + solutions[tv] = solve_one(lowers, uppers, []) 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: + candidate = AnyType(TypeOfAny.special_form) + res.append(candidate) + return res + + +def solve_non_linear( + vars: list[TypeVarId], constraints: list[Constraint], cmap: dict[TypeVarId, list[Constraint]] +) -> dict[TypeVarId, Type | None]: + """Solve set of constraints that may include non-linear ones, like T <: List[S]. - # Solve each type variable separately. + The whole algorithm consists of five steps: + * Propagate via linear constraints to get all possible constraints for each variable + * Find dependencies between type variables, group them in SCCs, and sort topologically + * Check 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 targets after each step. + """ + extra_constraints = [] 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) + extra_constraints.extend(propagate_constraints_for(tvar, SUBTYPE_OF, cmap)) + extra_constraints.extend(propagate_constraints_for(tvar, SUPERTYPE_OF, cmap)) + constraints += remove_dups(extra_constraints) + + # Recompute constraint map after propagating. + cmap = {tv: [] for tv in vars} + for con in constraints: + if con.type_var in vars: + cmap[con.type_var].append(con) + + dmap = compute_dependencies(cmap) + sccs = list(strongly_connected_components(set(vars), dmap)) + if all(check_linear(scc, cmap) for scc in sccs): + raw_batches = list(topsort(prepare_sccs(sccs, dmap))) + leafs = raw_batches[0] + free_vars = [] + for scc in leafs: + # If all constrain targets in this SCC are type variables within the + # same 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( + isinstance(c.target, TypeVarType) and c.target.id in vars + for tv in scc + for c in cmap[tv] + ): + # For convenience with current type application machinery, we randomly + # choose one of the existing type variables in SCC and designate it as free + # instead of defining a new type variable as a common solution. + # TODO: be careful about upper bounds (or values) when introducing free vars. + free_vars.append(sorted(scc, key=lambda x: x.raw_id)[0]) + + # 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: + solutions.update(solve_iteratively(flat_batch, cmap, free_vars)) + # We remove the solutions like T = T for free variables. This will indicate + # to the apply function, that they should not be touched. + # TODO: return list of free type variables explicitly, this logic is fragile + # (but if we do, we need to be careful everything works in incremental modes). + for tv in free_vars: + if tv in solutions: + del solutions[tv] + return solutions + return {} + + +def solve_iteratively( + batch: list[TypeVarId], cmap: dict[TypeVarId, list[Constraint]], free_vars: list[TypeVarId] +) -> dict[TypeVarId, Type | None]: + """Solve constraints sequentially, updating constraint targets after each step. + + We solve for type variables that appear in `batch`. If a constraint target is not constant + (i.e. constraint looks like T :> F[S, ...]), we substitute solutions found so far in + the target F[S, ...]. This way we can gradually solve for all variables in the batch taking + one solvable variable at a time (i.e. such a variable that has at least one constant bound). + + Importantly, variables in free_vars are considered constants, so for example if we have just + one initial constraint T <: List[S], we will have two SCCs {T} and {S}, then we first + designate S as free, and therefore T = List[S] is a valid solution for T. + """ + solutions = {} + relevant_constraints = [] + for tv in batch: + relevant_constraints.extend(cmap.get(tv, [])) + lowers, uppers = transitive_closure(batch, relevant_constraints) + s_batch = set(batch) + not_allowed_vars = [v for v in batch if v not in free_vars] + while s_batch: + for tv in s_batch: + if any(not get_vars(l, not_allowed_vars) for l in lowers[tv]) or any( + not get_vars(u, not_allowed_vars) for u in 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], not_allowed_vars) + solutions[solvable_tv] = result + if result is None: + # TODO: support backtracking lower/upper bound choices + # (will require switching this function from iterative to recursive). + continue + # Update the (transitive) constraints if there is a solution. + subs = {solvable_tv: result} + lowers = {tv: {expand_type(l, subs) for l in lowers[tv]} for tv in lowers} + uppers = {tv: {expand_type(u, subs) for u in uppers[tv]} for tv in uppers} + for v in cmap: + for c in cmap[v]: + c.target = expand_type(c.target, subs) + return solutions + + +def solve_one( + lowers: Iterable[Type], uppers: Iterable[Type], not_allowed_vars: list[TypeVarId] +) -> 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 + + # 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: + # There may be multiple steps needed to solve all vars within a + # (linear) SCC. We ignore targets pointing to not yet solved vars. + if get_vars(target, not_allowed_vars): + continue + 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: - 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)) + bottom = join_types(bottom, target) + + for target in uppers: + # Same as above. + if get_vars(target, not_allowed_vars): continue - elif bottom is None: - if top: - candidate = top + if top is None: + top = target + else: + 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 normalize_constraints( + constraints: list[Constraint], vars: list[TypeVarId] +) -> list[Constraint]: + """Normalize list of constraints (to simplify life for the non-linear solver). + + This includes two things currently: + * Complement T :> S by S <: T + * Remove strict duplicates + """ + res = constraints.copy() + for c in constraints: + if isinstance(c.target, TypeVarType): + res.append(Constraint(c.target, neg_op(c.op), c.origin_type_var)) + return [c for c in remove_dups(constraints) if c.type_var in vars] + + +def propagate_constraints_for( + var: TypeVarId, direction: int, cmap: dict[TypeVarId, list[Constraint]] +) -> list[Constraint]: + """Propagate via linear constraints to get additional constraints for `var`. + + For example if we have constraints: + [T <: int, S <: T, S :> str] + we can add two more + [S <: int, T :> str] + """ + extra_constraints = [] + seen = set() + front = [var] + if cmap[var]: + var_def = cmap[var][0].origin_type_var + else: + return [] + while front: + tv = front.pop(0) + for c in cmap[tv]: + if ( + isinstance(c.target, TypeVarType) + and c.target.id not in seen + and c.target.id in cmap + and c.op == direction + ): + front.append(c.target.id) + seen.add(c.target.id) + elif c.op == direction: + new_c = Constraint(var_def, direction, c.target) + if new_c not in cmap[var]: + extra_constraints.append(new_c) + return extra_constraints + + +def transitive_closure( + tvars: list[TypeVarId], constraints: list[Constraint] +) -> tuple[dict[TypeVarId, set[Type]], dict[TypeVarId, set[Type]]]: + """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. + + For example if we have initial constraints [T <: S, S <: U, U <: int], the transitive + closure is given by: + * {} <: T <: {S, U, int} + * {T} <: S <: {U, int} + * {T, S} <: U <: {int} + """ + # TODO: merge propagate_constraints_for() into this function. + # TODO: add secondary constraints here to make the algorithm complete. + uppers: dict[TypeVarId, set[Type]] = {tv: set() for tv in tvars} + lowers: dict[TypeVarId, set[Type]] = {tv: set() for tv in tvars} + graph: set[tuple[TypeVarId, TypeVarId]] = set() + + # Prime the closure with the initial trivial values. + for c in constraints: + if isinstance(c.target, TypeVarType) and c.target.id in tvars: + if c.op == SUBTYPE_OF: + graph.add((c.type_var, c.target.id)) 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 + graph.add((c.target.id, c.type_var)) + if c.op == SUBTYPE_OF: + uppers[c.type_var].add(c.target) else: - candidate = None - res.append(candidate) + lowers[c.type_var].add(c.target) + + # At this stage we know that constant bounds have been propagated already, so we + # only need to propagate linear constraints. + for c in constraints: + if isinstance(c.target, TypeVarType) and c.target.id in tvars: + if c.op == SUBTYPE_OF: + lower, upper = c.type_var, c.target.id + else: + lower, upper = c.target.id, c.type_var + extras = { + (l, u) for l in tvars for u in tvars if (l, lower) in graph and (upper, u) in graph + } + graph |= extras + 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] + return lowers, uppers + +def compute_dependencies( + cmap: dict[TypeVarId, list[Constraint]] +) -> 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 = {} + vars = list(cmap.keys()) + for tv in cmap: + deps = set() + for c in cmap[tv]: + deps |= get_vars(c.target, vars) + res[tv] = list(deps) return res + + +def check_linear(scc: set[TypeVarId], cmap: dict[TypeVarId, list[Constraint]]) -> 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(c.target, list(scc)) and not isinstance(c.target, TypeVarType) + for c in cmap[tv] + ): + return False + return True + + +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_type_vars(target)} & set(vars) diff --git a/mypy/state.py b/mypy/state.py index 2e44a936f8190..cd3a360dd15f3 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 b3a32c1ce72c6..b8803e03b9d2d 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:] diff --git a/mypy/strconv.py b/mypy/strconv.py index b2e9da5dbf6ae..c428addd43aa2 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) @@ -144,7 +154,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,11 +178,11 @@ 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: @@ -473,7 +483,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 +495,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 +507,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.type)})" 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 +523,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 +624,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 7c8751bbd6ed1..145f57fd77516 100644 --- a/mypy/stubdoc.py +++ b/mypy/stubdoc.py @@ -10,8 +10,8 @@ import io 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 # Type alias for signatures strings in format ('func_name', '(arg, opt_arg=False)'). Sig: _TypeAlias = Tuple[str, str] @@ -254,7 +254,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) diff --git a/mypy/stubgen.py b/mypy/stubgen.py index 6cb4669887fee..9084da2053cfb 100755 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -43,13 +43,13 @@ 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, Mapping import mypy.build import mypy.mixedtraverser @@ -73,13 +73,16 @@ ARG_STAR, ARG_STAR2, IS_ABSTRACT, + NOT_ABSTRACT, AssignmentStmt, Block, BytesExpr, CallExpr, ClassDef, ComparisonExpr, + ComplexExpr, Decorator, + DictExpr, EllipsisExpr, Expression, FloatExpr, @@ -102,6 +105,7 @@ TupleExpr, TypeInfo, UnaryExpr, + Var, ) from mypy.options import Options as MypyOptions from mypy.stubdoc import Sig, find_unique_signatures, parse_all_signatures @@ -122,9 +126,16 @@ 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, @@ -306,7 +317,7 @@ class AnnotationPrinter(TypeStrVisitor): # TODO: Generate valid string representation for callable types. # TODO: Use short names for Instances. def __init__(self, stubgen: StubGenerator) -> None: - super().__init__() + super().__init__(options=mypy.options.Options()) self.stubgen = stubgen def visit_any(self, t: AnyType) -> str: @@ -396,14 +407,24 @@ def visit_str_expr(self, node: StrExpr) -> str: def visit_index_expr(self, node: IndexExpr) -> str: 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 "..." @@ -638,8 +659,9 @@ def visit_mypy_file(self, o: MypyFile) -> None: self.referenced_names = find_referenced_names(o) known_imports = { "_typeshed": ["Incomplete"], - "typing": ["Any", "TypeVar"], + "typing": ["Any", "TypeVar", "NamedTuple"], "collections.abc": ["Generator"], + "typing_extensions": ["TypedDict", "ParamSpec", "TypeVarTuple"], } for pkg, imports in known_imports.items(): for t in imports: @@ -658,34 +680,30 @@ def visit_mypy_file(self, o: MypyFile) -> None: self.add(f"# {name}\n") 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: + def visit_func_def(self, o: FuncDef) -> 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) + or (self.is_recorded_name(o.name) and not o.is_overload) ): self.clear_decorators() return @@ -761,24 +779,28 @@ def visit_func_def( 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") + elif has_yield_expression(o) or has_yield_from_expression(o): + self.add_typing_import("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): + self.add_typing_import("Incomplete") + yield_name = send_name = self.typing_name("Incomplete") + else: + 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 = self.typing_name("Incomplete") + if in_assignment: + self.add_typing_import("Incomplete") + send_name = self.typing_name("Incomplete") if has_return_statement(o): self.add_typing_import("Incomplete") - return_name = "Incomplete" + return_name = self.typing_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: + elif not has_return_statement(o) and o.abstract_status == NOT_ABSTRACT: retname = "None" retfield = "" if retname is not None: @@ -794,140 +816,74 @@ def is_none_expr(self, expr: Expression) -> bool: 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) - is_abstract, _ = self.process_decorator(o) - self.visit_func_def(o.func, is_abstract=is_abstract) - - def process_decorator(self, o: Decorator) -> tuple[bool, bool]: + 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 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. - - 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 - 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 process_member_expr_decorator( - self, expr: MemberExpr, context: Decorator - ) -> tuple[bool, bool]: - """Process a function decorator of form @foo.bar. - - 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" - ) + 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"): + self.add_decorator(qualname, require_name=False) + + def get_fullname(self, expr: Expression) -> str: + """Return the full name resolving imports and import aliases.""" + 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) + 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 visit_class_def(self, o: ClassDef) -> None: self.method_names = find_method_names(o.defs.body) @@ -944,13 +900,7 @@ 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") @@ -976,16 +926,46 @@ def visit_class_def(self, o: ClassDef) -> 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 not None: + fields_str = ", ".join(f"({f!r}, {t})" for f, t in nt_fields) + namedtuple_name = self.typing_name("NamedTuple") + base_types.append(f"{namedtuple_name}({typename!r}, [{fields_str}])") + self.add_typing_import("NamedTuple") + else: + # Invalid namedtuple() call, cannot determine fields + base_types.append(self.typing_name("Incomplete")) + 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.typing_name("Incomplete")) + self.add_typing_import("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 visit_block(self, o: Block) -> None: @@ -998,21 +978,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 @@ -1038,36 +1022,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: + self.add_typing_import("Incomplete") + incomplete = self.typing_name("Incomplete") + return [(field_name, incomplete) for field_name in field_names] + 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 - self.import_tracker.require_name("NamedTuple") - self.add(f"{self._indent}class {lvalue.name}(NamedTuple):") - if len(items) == 0: - self.add(" ...\n") + + fields = self._get_namedtuple_fields(rvalue) + if fields is None: + self.annotate_as_incomplete(lvalue) + return + self.add_typing_import("NamedTuple") + bases = self.typing_name("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)) + self.add_typing_import("TypedDict") + 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.typing_name("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: + self.add_typing_import("Incomplete") + self.add(f"{self._indent}{lvalue.name}: {self.typing_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 +1159,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): @@ -1249,7 +1336,9 @@ def add(self, string: str) -> None: """Add text to generated stub.""" self._output.append(string) - def add_decorator(self, name: str) -> None: + def add_decorator(self, name: str, require_name: bool = False) -> None: + if require_name: + self.import_tracker.require_name(name) if not self._indent and self._state not in (EMPTY, FUNC): self._decorators.append("\n") self._decorators.append(f"{self._indent}@{name}\n") @@ -1265,15 +1354,7 @@ def typing_name(self, name: str) -> str: 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. + """Add a name to be imported for typing, unless it's imported already. The import will be internal to the stub. """ @@ -1285,11 +1366,6 @@ def add_import_line(self, line: str) -> None: 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 = "" @@ -1321,6 +1397,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,8 +1407,13 @@ 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": @@ -1342,6 +1425,40 @@ def get_str_type_of_node( else: return "" + def maybe_unwrap_unary_expr(self, expr: Expression) -> Expression: + """Unwrap (possibly nested) unary expressions. + + 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 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 print_annotation(self, t: Type) -> str: printer = AnnotationPrinter(self) return t.accept(printer) @@ -1575,6 +1692,14 @@ 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 + + # 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 +1713,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 ) @@ -1654,8 +1779,8 @@ def generate_stub_from_ast( file.write("".join(gen.output())) -def get_sig_generators(options: Options) -> List[SignatureGenerator]: - sig_generators: List[SignatureGenerator] = [ +def get_sig_generators(options: Options) -> list[SignatureGenerator]: + sig_generators: list[SignatureGenerator] = [ DocstringSignatureGenerator(), FallbackSignatureGenerator(), ] @@ -1707,6 +1832,7 @@ def generate_stubs(options: Options) -> None: ) # Separately analyse C modules using different logic. + all_modules = sorted(m.module for m in (py_modules + c_modules)) for mod in c_modules: if any(py_mod.module.startswith(mod.module + ".") for py_mod in py_modules + c_modules): target = mod.module.replace(".", "/") + "/__init__.pyi" @@ -1715,7 +1841,9 @@ def generate_stubs(options: Options) -> None: 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) + generate_stub_for_c_module( + mod.module, target, known_modules=all_modules, sig_generators=sig_generators + ) num_modules = len(py_modules) + len(c_modules) if not options.quiet and num_modules > 0: print("Processed %d modules" % num_modules) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index add33e66cee38..8aa1fb3d2c0a8 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -12,8 +12,7 @@ import re from abc import abstractmethod from types import ModuleType -from typing import Any, Iterable, Mapping -from typing_extensions import Final +from typing import Any, Final, Iterable, Mapping from mypy.moduleinspect import is_c_module from mypy.stubdoc import ( @@ -44,6 +43,17 @@ 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, func: object, module_name: str, name: str @@ -52,7 +62,7 @@ def get_function_sig( @abstractmethod def get_method_sig( - self, func: object, module_name: str, class_name: str, name: str, self_var: str + self, cls: type, func: object, module_name: str, class_name: str, name: str, self_var: str ) -> list[FunctionSig] | None: pass @@ -83,7 +93,7 @@ def get_function_sig( return None def get_method_sig( - self, func: object, module_name: str, class_name: str, name: str, self_var: str + self, cls: type, func: object, module_name: str, class_name: str, name: str, self_var: str ) -> list[FunctionSig] | None: if ( name in ("__new__", "__init__") @@ -94,10 +104,11 @@ def get_method_sig( FunctionSig( name=name, args=infer_arg_sig_from_anon_docstring(self.class_sigs[class_name]), - ret_type="None" if name == "__init__" else "Any", + ret_type=infer_method_ret_type(name), ) ] - return self.get_function_sig(func, module_name, name) + inferred = self.get_function_sig(func, module_name, name) + return self.remove_self_type(inferred, self_var) class DocstringSignatureGenerator(SignatureGenerator): @@ -114,9 +125,19 @@ def get_function_sig( return inferred def get_method_sig( - self, func: object, module_name: str, class_name: str, name: str, self_var: str + self, + cls: type, + func: object, + module_name: str, + class_name: str, + func_name: str, + self_var: str, ) -> list[FunctionSig] | None: - return self.get_function_sig(func, module_name, name) + inferred = self.get_function_sig(func, module_name, func_name) + if not inferred and func_name == "__init__": + # look for class-level constructor signatures of the form () + inferred = self.get_function_sig(cls, module_name, class_name) + return self.remove_self_type(inferred, self_var) class FallbackSignatureGenerator(SignatureGenerator): @@ -132,19 +153,22 @@ def get_function_sig( ] def get_method_sig( - self, func: object, module_name: str, class_name: str, name: str, self_var: str + self, cls: type, 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", + args=infer_method_args(name, self_var), + ret_type=infer_method_ret_type(name), ) ] def generate_stub_for_c_module( - module_name: str, target: str, sig_generators: Iterable[SignatureGenerator] + module_name: str, + target: str, + known_modules: list[str], + sig_generators: Iterable[SignatureGenerator], ) -> None: """Generate stub for C module. @@ -166,11 +190,17 @@ def generate_stub_for_c_module( imports: list[str] = [] functions: list[str] = [] done = set() - items = sorted(module.__dict__.items(), key=lambda x: x[0]) + items = sorted(get_members(module), 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 + module, + name, + obj, + output=functions, + known_modules=known_modules, + imports=imports, + sig_generators=sig_generators, ) done.add(name) types: list[str] = [] @@ -179,7 +209,13 @@ def generate_stub_for_c_module( continue if is_c_type(obj): generate_c_type_stub( - module, name, obj, types, imports=imports, sig_generators=sig_generators + module, + name, + obj, + output=types, + known_modules=known_modules, + imports=imports, + sig_generators=sig_generators, ) done.add(name) variables = [] @@ -187,7 +223,9 @@ def generate_stub_for_c_module( 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) + type_str = strip_or_import( + get_type_fullname(type(obj)), module, known_modules, imports + ) variables.append(f"{name}: {type_str}") output = sorted(set(imports)) for line in variables: @@ -215,7 +253,23 @@ def add_typing_import(output: list[str]) -> list[str]: if names: return [f"from typing import {', '.join(names)}", ""] + output else: - return output[:] + return output.copy() + + +def get_members(obj: object) -> list[tuple[str, Any]]: + obj_dict: Mapping[str, Any] = getattr(obj, "__dict__") # noqa: B009 + results = [] + for name in obj_dict: + if 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 is_c_function(obj: object) -> bool: @@ -257,10 +311,13 @@ def generate_c_function_stub( module: ModuleType, name: str, obj: object, + *, + known_modules: list[str], + sig_generators: Iterable[SignatureGenerator], output: list[str], imports: list[str], - sig_generators: Iterable[SignatureGenerator], self_var: str | None = None, + cls: type | None = None, class_name: str | None = None, ) -> None: """Generate stub for a single function or method. @@ -273,13 +330,16 @@ def generate_c_function_stub( inferred: list[FunctionSig] | None = None if class_name: # method: + assert cls is not None, "cls should be provided for methods" 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) + inferred = sig_gen.get_method_sig( + cls, 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: + if not sig.args or sig.args[0].name not in ("self", "cls"): sig.args.insert(0, ArgSig(name=self_var)) break else: @@ -295,7 +355,6 @@ def generate_c_function_stub( "if FallbackSignatureGenerator is provided" ) - is_classmethod = self_var == "cls" is_overloaded = len(inferred) > 1 if inferred else False if is_overloaded: imports.append("from typing import overload") @@ -303,35 +362,35 @@ def generate_c_function_stub( 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 + 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.type: + arg_def += ": " + strip_or_import(arg.type, module, known_modules, imports) - if arg.default: - arg_def += " = ..." + if arg.default: + arg_def += " = ..." args.append(arg_def) if is_overloaded: output.append("@overload") - if is_classmethod: + # a sig generator indicates @classmethod by specifying the cls arg + if class_name and signature.args and signature.args[0].name == "cls": output.append("@classmethod") output.append( "def {function}({args}) -> {ret}: ...".format( function=name, args=", ".join(args), - ret=strip_or_import(signature.ret_type, module, imports), + ret=strip_or_import(signature.ret_type, module, known_modules, imports), ) ) -def strip_or_import(typ: str, module: ModuleType, imports: list[str]) -> str: +def strip_or_import( + typ: str, module: ModuleType, known_modules: list[str], imports: list[str] +) -> str: """Strips unnecessary module names from typ. If typ represents a type that is inside module or is a type coming from builtins, remove @@ -340,21 +399,33 @@ def strip_or_import(typ: str, module: ModuleType, imports: list[str]) -> str: Arguments: typ: name of the type module: in which this type is used + known_modules: other modules being processed imports: list of import statements (may be modified during the call) """ + local_modules = ["builtins"] + if module: + local_modules.append(module.__name__) + 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 :] + stripped_subtyp = strip_or_import(subtyp.strip(), module, known_modules, imports) + if stripped_subtyp != subtyp: + stripped_type = re.sub( + r"(^|[\[, ]+)" + re.escape(subtyp) + r"($|[\], ]+)", + r"\1" + stripped_subtyp + r"\2", + stripped_type, + ) elif "." in typ: - arg_module = typ[: typ.rindex(".")] - if arg_module == "builtins": - stripped_type = typ[len("builtins") + 1 :] + for module_name in local_modules + list(reversed(known_modules)): + if typ.startswith(module_name + "."): + if module_name in local_modules: + stripped_type = typ[len(module_name) + 1 :] + arg_module = module_name + break else: + arg_module = typ[: typ.rindex(".")] + if arg_module not in local_modules: imports.append(f"import {arg_module}") if stripped_type == "NoneType": stripped_type = "None" @@ -373,6 +444,7 @@ def generate_c_property_stub( ro_properties: list[str], readonly: bool, module: ModuleType | None = None, + known_modules: list[str] | None = None, imports: list[str] | None = None, ) -> None: """Generate property stub using introspection of 'obj'. @@ -392,10 +464,6 @@ def infer_prop_type(docstr: str | None) -> str | None: else: return None - # 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) @@ -403,8 +471,8 @@ def infer_prop_type(docstr: str | None) -> str | None: if not inferred: inferred = "Any" - if module is not None and imports is not None: - inferred = strip_or_import(inferred, module, imports) + if module is not None and imports is not None and known_modules is not None: + inferred = strip_or_import(inferred, module, known_modules, imports) if is_static_property(obj): trailing_comment = " # read-only" if readonly else "" @@ -422,6 +490,7 @@ def generate_c_type_stub( class_name: str, obj: type, output: list[str], + known_modules: list[str], imports: list[str], sig_generators: Iterable[SignatureGenerator], ) -> None: @@ -430,69 +499,75 @@ def generate_c_type_stub( 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])) + raw_lookup = getattr(obj, "__dict__") # noqa: B009 + items = sorted(get_members(obj), 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] = [] - done: set[str] = set() + attrs: list[tuple[str, Any]] = [] for attr, value in items: + # use unevaluated descriptors when dealing with property inspection + raw_value = raw_lookup.get(attr, value) if is_c_method(value) or is_c_classmethod(value): - done.add(attr) - if not is_skipped_attribute(attr): - if attr == "__new__": - # TODO: We should support __new__. - if "__init__" in obj_dict: - # 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" - else: - self_var = "self" - generate_c_function_stub( - module, - attr, - 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( + if attr == "__new__": + # TODO: We should support __new__. + 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" + else: + self_var = "self" + generate_c_function_stub( + module, attr, value, + output=methods, + known_modules=known_modules, + imports=imports, + self_var=self_var, + cls=obj, + class_name=class_name, + sig_generators=sig_generators, + ) + elif is_c_property(raw_value): + generate_c_property_stub( + attr, + raw_value, static_properties, rw_properties, ro_properties, - is_c_property_readonly(value), + is_c_property_readonly(raw_value), module=module, + known_modules=known_modules, imports=imports, ) elif is_c_type(value): generate_c_type_stub( - module, attr, value, types, imports=imports, sig_generators=sig_generators + module, + attr, + value, + types, + imports=imports, + known_modules=known_modules, + sig_generators=sig_generators, ) - done.add(attr) + else: + attrs.append((attr, value)) - 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) - ) + for attr, value in attrs: + static_properties.append( + "{}: ClassVar[{}] = ...".format( + attr, + strip_or_import(get_type_fullname(type(value)), module, known_modules, imports), ) + ) all_bases = type.mro(obj) if all_bases[-1] is object: # TODO: Is this always object? @@ -510,7 +585,8 @@ def generate_c_type_stub( bases.append(base) if bases: bases_str = "(%s)" % ", ".join( - strip_or_import(get_type_fullname(base), module, imports) for base in bases + strip_or_import(get_type_fullname(base), module, known_modules, imports) + for base in bases ) else: bases_str = "" @@ -559,6 +635,7 @@ def is_pybind_skipped_attribute(attr: str) -> bool: def is_skipped_attribute(attr: str) -> bool: return attr in ( + "__class__", "__getattribute__", "__str__", "__repr__", @@ -571,7 +648,7 @@ def is_skipped_attribute(attr: str) -> bool: ) -def infer_method_sig(name: str, self_var: str | None = None) -> list[ArgSig]: +def infer_method_args(name: str, self_var: str | None = None) -> list[ArgSig]: args: list[ArgSig] | None = None if name.startswith("__") and name.endswith("__"): name = name[2:-2] @@ -673,3 +750,18 @@ def infer_method_sig(name: str, self_var: str | None = None) -> list[ArgSig]: if args is None: args = [ArgSig(name="*args"), ArgSig(name="**kwargs")] return [ArgSig(name=self_var or "self")] + args + + +def infer_method_ret_type(name: str) -> str: + if name.startswith("__") and name.endswith("__"): + name = name[2:-2] + if name in ("float", "bool", "bytes", "int"): + 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", "hash", "sizeof", "trunc", "floor", "ceil"): + return "int" + elif name in ("init", "setitem"): + return "None" + return "Any" diff --git a/mypy/stubinfo.py b/mypy/stubinfo.py index 15bd96d9f4b4c..e6e549ad280f4 100644 --- a/mypy/stubinfo.py +++ b/mypy/stubinfo.py @@ -20,8 +20,6 @@ def stub_package_name(prefix: str) -> str: # 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", @@ -76,8 +74,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. @@ -180,4 +176,9 @@ def stub_package_name(prefix: str) -> str: "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 cd173f63e2a1b..906a8c923b37a 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -25,8 +25,8 @@ 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 Any, Generic, Iterator, TypeVar, Union +from typing_extensions import get_origin, is_typeddict import mypy.build import mypy.modulefinder @@ -209,7 +209,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(): @@ -419,14 +424,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. @@ -470,18 +490,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 +513,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 +543,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 +572,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" @@ -646,7 +677,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": @@ -933,11 +964,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) @@ -1040,9 +1076,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 +1115,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 +1156,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 +1185,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. @@ -1337,12 +1402,15 @@ 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__", # typing implementation details, consider removing some of these: "__parameters__", "__origin__", "__args__", "__orig_bases__", - "__final__", + "__final__", # Has a specialized check # Consider removing __slots__? "__slots__", } @@ -1611,7 +1679,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 diff --git a/mypy/subtypes.py b/mypy/subtypes.py index c3d5517d43dd2..a6dc071f92b00 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1,8 +1,8 @@ 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 @@ -58,10 +58,10 @@ UninhabitedType, UnionType, UnpackType, - _flattened, get_proper_type, is_named_instance, ) +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 @@ -278,11 +278,7 @@ 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) - ): + if not proper_subtype and isinstance(right, (AnyType, UnboundType, ErasedType)): # TODO: should we consider all types proper subtypes of UnboundType and/or # ErasedType as we do for non-proper subtyping. return True @@ -388,8 +384,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 +435,13 @@ 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, 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( @@ -599,11 +596,17 @@ def check_mixed( 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 ): 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 @@ -658,10 +661,12 @@ def visit_type_var_tuple(self, left: TypeVarTupleType) -> bool: def visit_unpack_type(self, left: UnpackType) -> bool: 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): + if isinstance(self.right, (Parameters, CallableType)): right = self.right if isinstance(right, CallableType): right = right.with_unpacked_kwargs() @@ -689,7 +694,9 @@ def visit_callable_type(self, left: CallableType) -> bool: right, is_compat=self._is_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 True, ) elif isinstance(right, Overloaded): return all(self._is_subtype(left, item) for item in right.items) @@ -746,7 +753,9 @@ def visit_tuple_type(self, left: TupleType) -> bool: # 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): + 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): @@ -851,7 +860,11 @@ 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 True + ) if left_index not in matched_overloads and ( is_callable_compatible( left_item, @@ -908,15 +921,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) @@ -1027,7 +1036,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 +1048,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) @@ -1730,7 +1724,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 +1797,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: diff --git a/mypy/suggestions.py b/mypy/suggestions.py index 9ac033ba3bdf0..8e1225f00a2fa 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_optional, 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. @@ -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 diff --git a/mypy/test/data.py b/mypy/test/data.py index 6e2ad198f614d..66dafaff775a1 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. @@ -65,22 +76,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 +116,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 +141,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): @@ -130,29 +161,26 @@ def parse_test_case(case: DataDrivenTestCase) -> None: 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: @@ -169,10 +197,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}:{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: @@ -184,19 +213,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,6 +241,7 @@ def parse_test_case(case: DataDrivenTestCase) -> None: case.triggered = triggered or [] case.normalize_output = normalize_output case.expected_fine_grained_targets = targets + case.test_modules = test_modules class DataDrivenTestCase(pytest.Item): @@ -217,6 +252,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 @@ -225,6 +261,8 @@ 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]] @@ -331,27 +369,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}" @@ -378,6 +422,7 @@ def module_from_path(path: str) -> str: return module +@dataclass class TestItem: """Parsed test caseitem. @@ -386,20 +431,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]: @@ -421,7 +464,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] @@ -442,7 +485,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 @@ -459,7 +502,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 @@ -578,8 +621,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. @@ -597,6 +647,16 @@ def pytest_pycollect_makeitem(collector: Any, name: str, obj: object) -> Any | N 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"(-(?Pposix|windows))?" + r"(?P-skip)?" + r"(?P-xfail)?" +) + + def split_test_cases( parent: DataFileCollector, suite: DataSuite, file: str ) -> Iterator[DataDrivenTestCase]: @@ -607,40 +667,33 @@ 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")), data=data, line=line_no, ) @@ -665,6 +718,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. @@ -673,6 +732,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] @@ -688,6 +749,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 145027404ff79..d2c92614048ad 100644 --- a/mypy/test/helpers.py +++ b/mypy/test/helpers.py @@ -46,17 +46,9 @@ def run_mypy(args: list[str]) -> None: def assert_string_arrays_equal(expected: list[str], actual: list[str], msg: str) -> None: """Assert that two string arrays are equal. - We consider "can't" and "cannot" equivalent, by replacing the - former with the latter before comparing. - Display any differences in a human-readable form. """ - __tracebackhide__ = True - 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) @@ -117,7 +109,7 @@ 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) + pytest.fail(msg, pytrace=False) def assert_module_equivalence(name: str, expected: Iterable[str], actual: Iterable[str]) -> None: @@ -143,39 +135,6 @@ def assert_target_equivalence(name: str, expected: list[str], actual: list[str]) ) -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 +217,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: @@ -380,10 +339,10 @@ 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): 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/test_parse_data.py b/mypy/test/meta/test_parse_data.py new file mode 100644 index 0000000000000..6593dbc45704e --- /dev/null +++ b/mypy/test/meta/test_parse_data.py @@ -0,0 +1,103 @@ +""" +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. +""" +import subprocess +import sys +import textwrap +import uuid +from pathlib import Path + +from mypy.test.config import test_data_prefix +from mypy.test.helpers import Suite + + +class ParseTestDataSuite(Suite): + def _dedent(self, s: str) -> str: + return textwrap.dedent(s).lstrip() + + def _run_pytest(self, data_suite: str) -> str: + p_test_data = Path(test_data_prefix) + p_root = p_test_data.parent.parent + p = p_test_data / f"check-meta-{uuid.uuid4()}.test" + assert not p.exists() + try: + p.write_text(data_suite) + test_nodeid = f"mypy/test/testcheck.py::TypeCheckSuite::{p.name}" + args = [sys.executable, "-m", "pytest", "-n", "0", "-s", test_nodeid] + proc = subprocess.run(args, cwd=p_root, capture_output=True, check=False) + return proc.stdout.decode() + finally: + p.unlink() + + def test_parse_invalid_case(self) -> None: + # Arrange + data = self._dedent( + """ + [case abc] + s: str + [case foo-XFAIL] + s: str + """ + ) + + # Act + actual = self._run_pytest(data) + + # Assert + assert "Invalid testcase id 'foo-XFAIL'" in actual + + def test_parse_invalid_section(self) -> None: + # Arrange + data = self._dedent( + """ + [case abc] + s: str + [unknownsection] + abc + """ + ) + + # Act + actual = self._run_pytest(data) + + # Assert + expected_lineno = data.splitlines().index("[unknownsection]") + 1 + expected = ( + f".test:{expected_lineno}: Invalid section header [unknownsection] in case 'abc'" + ) + assert expected in actual + + def test_bad_ge_version_check(self) -> None: + # Arrange + data = self._dedent( + """ + [case abc] + s: str + [out version>=3.8] + abc + """ + ) + + # Act + actual = self._run_pytest(data) + + # Assert + assert "version>=3.8 always true since minimum runtime version is (3, 8)" in actual + + def test_bad_eq_version_check(self) -> None: + # Arrange + data = self._dedent( + """ + [case abc] + s: str + [out version==3.7] + abc + """ + ) + + # Act + actual = self._run_pytest(data) + + # Assert + assert "version==3.7 always false since minimum runtime version is (3, 8)" in actual diff --git a/mypy/test/meta/test_update_data.py b/mypy/test/meta/test_update_data.py new file mode 100644 index 0000000000000..4e4bdd193dbfa --- /dev/null +++ b/mypy/test/meta/test_update_data.py @@ -0,0 +1,157 @@ +""" +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. +""" +import shlex +import subprocess +import sys +import textwrap +import uuid +from pathlib import Path + +from mypy.test.config import test_data_prefix +from mypy.test.helpers import Suite + + +class UpdateDataSuite(Suite): + def _run_pytest_update_data(self, data_suite: str, *, max_attempts: int) -> str: + """ + 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). + """ + p_test_data = Path(test_data_prefix) + p_root = p_test_data.parent.parent + p = p_test_data / f"check-meta-{uuid.uuid4()}.test" + assert not p.exists() + try: + p.write_text(textwrap.dedent(data_suite).lstrip()) + + test_nodeid = f"mypy/test/testcheck.py::TypeCheckSuite::{p.name}" + args = [sys.executable, "-m", "pytest", "-n", "0", "-s", "--update-data", test_nodeid] + cmd = shlex.join(args) + for i in range(max_attempts - 1, -1, -1): + res = subprocess.run(args, cwd=p_root) + if res.returncode == 0: + break + print(f"`{cmd}` returned {res.returncode}: {i} attempts remaining") + + return p.read_text() + finally: + p.unlink() + + 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. + actual = self._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] + """, + max_attempts=3, + ) + + # Assert + expected = """ + [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 actual == textwrap.dedent(expected).lstrip() diff --git a/mypy/test/test_ref_info.py b/mypy/test/test_ref_info.py new file mode 100644 index 0000000000000..05052e4916577 --- /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/testcheck.py b/mypy/test/testcheck.py index 4fe2ee6393c0b..7b81deeafe9d2 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -11,7 +11,6 @@ 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,8 +22,8 @@ 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] @@ -37,9 +36,7 @@ # 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): @@ -85,6 +82,19 @@ 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, @@ -118,14 +128,16 @@ def run_case_once( 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 +172,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 {})" - output = testcase.output - elif incremental_step == 1: - msg = "Unexpected type checker output in incremental, run 1 ({}, 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: + 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 +200,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 2e8b0dc9a1cd5..30ecef07a821a 100644 --- a/mypy/test/testcmdline.py +++ b/mypy/test/testcmdline.py @@ -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/testdaemon.py b/mypy/test/testdaemon.py index e3cdf44d89f2f..7115e682e60da 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 3343762cfaafd..f9a059672de8d 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/testfinegrained.py b/mypy/test/testfinegrained.py index b19c49bf60bc2..ba0526d325588 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 @@ -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: @@ -169,7 +165,8 @@ def get_options(self, source: str, testcase: DataDrivenTestCase, build_cache: bo 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"]) + 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) diff --git a/mypy/test/testgraph.py b/mypy/test/testgraph.py index b145d92aea6cd..b0d148d5ae9c6 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( diff --git a/mypy/test/testmerge.py b/mypy/test/testmerge.py index 595aba49d8b7e..0582c9ed58822 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/testparse.py b/mypy/test/testparse.py index 6a2d1e145251c..0140eb0728216 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.config_parser import parse_mypy_comments from mypy.errors import CompileError 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): @@ -32,6 +34,7 @@ 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"): @@ -39,15 +42,18 @@ def test_parser(testcase: DataDrivenTestCase) -> None: 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"), - fnam="main", - module="__main__", - errors=None, - options=options, + bytes(source, "ascii"), fnam="main", module="__main__", errors=None, options=options ) - a = str(n).split("\n") + a = n.str_with_options(options).split("\n") except CompileError as e: a = e.messages assert_string_arrays_equal( diff --git a/mypy/test/testpep561.py b/mypy/test/testpep561.py index 1602bae6a51f3..48d0658cd1e91 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) diff --git a/mypy/test/testpythoneval.py b/mypy/test/testpythoneval.py index 02dd116553821..17baec96cfbc9 100644 --- a/mypy/test/testpythoneval.py +++ b/mypy/test/testpythoneval.py @@ -46,21 +46,30 @@ 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", ] 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/testsemanal.py b/mypy/test/testsemanal.py index 71ebc43df8c26..3455f41aa20a0 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 @@ -47,6 +46,7 @@ def get_semanal_options(program_text: str, testcase: DataDrivenTestCase) -> Opti 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 +77,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 +146,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 +181,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 +204,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/teststubgen.py b/mypy/test/teststubgen.py index c7b576f89389e..79d380785a39d 100644 --- a/mypy/test/teststubgen.py +++ b/mypy/test/teststubgen.py @@ -38,7 +38,8 @@ generate_c_function_stub, generate_c_property_stub, generate_c_type_stub, - infer_method_sig, + infer_method_args, + infer_method_ret_type, is_c_property_readonly, ) from mypy.stubutil import common_dir_prefix, remove_misplaced_type_comments, walk_packages @@ -676,6 +677,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) @@ -768,16 +770,18 @@ class StubgencSuite(unittest.TestCase): """ def test_infer_hash_sig(self) -> None: - assert_equal(infer_method_sig("__hash__"), [self_arg]) + assert_equal(infer_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_method_args("__getitem__"), [self_arg, ArgSig(name="index")]) def test_infer_setitem_sig(self) -> None: assert_equal( - infer_method_sig("__setitem__"), + infer_method_args("__setitem__"), [self_arg, ArgSig(name="index"), ArgSig(name="object")], ) + assert_equal(infer_method_ret_type("__setitem__"), "None") def test_infer_binary_op_sig(self) -> None: for op in ( @@ -794,11 +798,19 @@ def test_infer_binary_op_sig(self) -> None: "mul", "rmul", ): - assert_equal(infer_method_sig(f"__{op}__"), [self_arg, ArgSig(name="other")]) + assert_equal(infer_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_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: output: list[str] = [] @@ -809,7 +821,8 @@ def test_generate_c_type_stub_no_crash_for_object(self) -> None: "alias", object, output, - imports, + imports=imports, + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal(imports, []) @@ -828,7 +841,8 @@ class TestClassVariableCls: "C", TestClassVariableCls, output, - imports, + imports=imports, + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal(imports, []) @@ -846,7 +860,8 @@ class TestClass(KeyError): "C", TestClass, output, - imports, + imports=imports, + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal(output, ["class C(KeyError): ..."]) @@ -861,7 +876,8 @@ def test_generate_c_type_inheritance_same_module(self) -> None: "C", TestClass, output, - imports, + imports=imports, + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal(output, ["class C(TestBaseClass): ..."]) @@ -881,7 +897,8 @@ class TestClass(argparse.Action): "C", TestClass, output, - imports, + imports=imports, + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal(output, ["class C(argparse.Action): ..."]) @@ -899,7 +916,8 @@ class TestClass(type): "C", TestClass, output, - imports, + imports=imports, + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal(output, ["class C(type): ..."]) @@ -919,10 +937,12 @@ def test(self, arg0: str) -> None: mod, "test", TestClass.test, - output, - imports, + output=output, + imports=imports, self_var="self", + cls=TestClass, class_name="TestClass", + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal(output, ["def test(self, arg0: int) -> Any: ..."]) @@ -942,10 +962,12 @@ def test(self, arg0: str) -> None: mod, "test", TestClass.test, - output, - imports, + output=output, + imports=imports, self_var="self", + cls=TestClass, class_name="TestClass", + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal(output, ["def test(self, arg0: int) -> Any: ..."]) @@ -964,10 +986,12 @@ def test(cls, arg0: str) -> None: mod, "test", TestClass.test, - output, - imports, + output=output, + imports=imports, self_var="cls", + cls=TestClass, class_name="TestClass", + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal(output, ["@classmethod", "def test(cls, *args, **kwargs) -> Any: ..."]) @@ -990,10 +1014,12 @@ def test(self, arg0: str) -> None: mod, "test", TestClass.test, - output, - imports, + output=output, + imports=imports, self_var="cls", + cls=TestClass, class_name="TestClass", + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal( @@ -1023,10 +1049,12 @@ def test(self, arg0: str = "") -> None: mod, "test", TestClass.test, - output, - imports, + output=output, + imports=imports, self_var="self", + cls=TestClass, class_name="TestClass", + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal(output, ["def test(self, arg0: str = ...) -> Any: ..."]) @@ -1034,6 +1062,7 @@ def test(self, arg0: str = "") -> None: 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: @@ -1048,22 +1077,24 @@ def test(arg0: str) -> None: mod, "test", test, - output, - imports, + output=output, + imports=imports, + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal(output, ["def test(arg0: argparse.Action) -> Any: ..."]) assert_equal(imports, ["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] = [] @@ -1073,19 +1104,20 @@ def test(arg0: str) -> None: mod, "test", test, - output, - imports, + output=output, + imports=imports, + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) - assert_equal(output, ["def test(arg0: Action) -> Any: ..."]) + assert_equal(output, ["def test(arg0: Action) -> Action: ..."]) assert_equal(imports, []) - 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] = [] @@ -1095,21 +1127,24 @@ def test(arg0: str) -> None: mod, "test", test, - output, - imports, + output=output, + imports=imports, + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) - assert_equal(output, ["def test(arg0: str) -> argparse.Action: ..."]) - assert_equal(imports, ["import argparse"]) + assert_equal(output, ["def test(arg0: argparse.Action) -> argparse.Action: ..."]) + assert_equal(set(imports), {"import argparse"}) - 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. + 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: str) -> argparse.Action + test(arg0: list[argparse.Action]) -> list[argparse.Action] """ output: list[str] = [] @@ -1119,11 +1154,84 @@ def test(arg0: str) -> None: mod, "test", test, - output, - imports, + output=output, + imports=imports, + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) - assert_equal(output, ["def test(arg0: str) -> Action: ..."]) + assert_equal(output, ["def test(arg0: list[Action]) -> list[Action]: ..."]) + assert_equal(imports, []) + + 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: 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=output, + imports=imports, + known_modules=[mod.__name__], + sig_generators=get_sig_generators(parse_options([])), + ) + assert_equal(output, ["def test(arg0: Union[Action,None]) -> Tuple[Action,None]: ..."]) + assert_equal(imports, []) + + 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] = [] + imports: list[str] = [] + mod = ModuleType(self.__module__, "") + generate_c_function_stub( + mod, + "test", + test, + output=output, + imports=imports, + known_modules=["foo", "foo.spangle", "bar"], + sig_generators=get_sig_generators(parse_options([])), + ) + assert_equal(output, ["def test(arg0: foo.bar.Action) -> other.Thing: ..."]) + assert_equal(set(imports), {"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] = [] + imports: list[str] = [] + mod = ModuleType(self.__module__, "") + generate_c_function_stub( + mod, + "test", + test, + output=output, + imports=imports, + known_modules=[mod.__name__], + sig_generators=get_sig_generators(parse_options([])), + ) + assert_equal(output, ["def test(*args, **kwargs) -> Any: ..."]) assert_equal(imports, []) def test_generate_c_property_with_pybind11(self) -> None: @@ -1190,10 +1298,12 @@ def test(self, arg0: str) -> None: mod, "test", TestClass.test, - output, - imports, + output=output, + imports=imports, self_var="self", + cls=TestClass, class_name="TestClass", + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal(output, ["def test(self, arg0: List[int]) -> Any: ..."]) @@ -1213,10 +1323,12 @@ def test(self, arg0: str) -> None: mod, "test", TestClass.test, - output, - imports, + output=output, + imports=imports, self_var="self", + cls=TestClass, class_name="TestClass", + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal(output, ["def test(self, arg0: Dict[str,int]) -> Any: ..."]) @@ -1236,10 +1348,12 @@ def test(self, arg0: str) -> None: mod, "test", TestClass.test, - output, - imports, + output=output, + imports=imports, self_var="self", + cls=TestClass, class_name="TestClass", + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal(output, ["def test(self, arg0: Dict[str,List[int]]) -> Any: ..."]) @@ -1259,10 +1373,12 @@ def test(self, arg0: str) -> None: mod, "test", TestClass.test, - output, - imports, + output=output, + imports=imports, self_var="self", + cls=TestClass, class_name="TestClass", + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal(output, ["def test(self, arg0: Dict[argparse.Action,int]) -> Any: ..."]) @@ -1282,10 +1398,12 @@ def test(self, arg0: str) -> None: mod, "test", TestClass.test, - output, - imports, + output=output, + imports=imports, self_var="self", + cls=TestClass, class_name="TestClass", + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal(output, ["def test(self, arg0: Dict[str,argparse.Action]) -> Any: ..."]) @@ -1310,10 +1428,12 @@ def __init__(self, arg0: str) -> None: mod, "__init__", TestClass.__init__, - output, - imports, + output=output, + imports=imports, self_var="self", + cls=TestClass, class_name="TestClass", + known_modules=[mod.__name__], sig_generators=get_sig_generators(parse_options([])), ) assert_equal( @@ -1329,6 +1449,42 @@ def __init__(self, arg0: str) -> None: ) assert_equal(set(imports), {"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] = [] + imports: list[str] = [] + mod = ModuleType(TestClass.__module__, "") + generate_c_function_stub( + mod, + "__init__", + TestClass.__init__, + output=output, + imports=imports, + self_var="self", + cls=TestClass, + class_name="TestClass", + known_modules=[mod.__name__], + sig_generators=get_sig_generators(parse_options([])), + ) + assert_equal( + output, + [ + "@overload", + "def __init__(self, arg0: str) -> None: ...", + "@overload", + "def __init__(self, arg0: str, arg1: str) -> None: ...", + ], + ) + assert_equal(set(imports), {"from typing import overload"}) + class ArgSigSuite(unittest.TestCase): def test_repr(self) -> None: diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 6bb4dfb2c937d..661d46e9fd8af 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] @@ -123,11 +123,10 @@ 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, "") ) @@ -233,17 +232,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 +282,21 @@ 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_default_presence(self) -> Iterator[Case]: @@ -583,17 +580,16 @@ 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=""" - @overload - def f5(__a: int) -> int: ... - @overload - def f5(__b: str) -> str: ... - """, - runtime="def f5(x, /): pass", - error=None, - ) + yield Case( + stub=""" + @overload + def f5(__a: int) -> int: ... + @overload + def f5(__b: str) -> str: ... + """, + runtime="def f5(x, /): pass", + error=None, + ) @collect_cases def test_property(self) -> Iterator[Case]: @@ -765,6 +761,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 +788,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 +808,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 +821,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 +836,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 +852,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 +887,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,37 +920,32 @@ 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, ) @@ -1139,6 +1114,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 +1568,36 @@ 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_type_var(self) -> Iterator[Case]: @@ -1436,7 +1680,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 +1712,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, @@ -1566,7 +1862,7 @@ def test_output(self) -> None: 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 +1873,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( @@ -1656,13 +1952,13 @@ def also_bad(asdf): pass 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) ) @@ -1719,7 +2015,7 @@ def test_config_file(self) -> None: 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: in file {TEST_MODULE_NAME}.pyi:2\n_decimal.Decimal\nRuntime:\n5\n\n" "Found 1 error (checked 1 module)\n" diff --git a/mypy/test/testsubtypes.py b/mypy/test/testsubtypes.py index c76a34ff00d7f..464f64d2b846d 100644 --- a/mypy/test/testsubtypes.py +++ b/mypy/test/testsubtypes.py @@ -2,7 +2,7 @@ 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 @@ -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() diff --git a/mypy/test/testtransform.py b/mypy/test/testtransform.py index 1d3d4468444e7..ba9fe8668fb44 100644 --- a/mypy/test/testtransform.py +++ b/mypy/test/testtransform.py @@ -2,8 +2,6 @@ from __future__ import annotations -import os.path - from mypy import build from mypy.errors import CompileError from mypy.modulefinder import BuildSource @@ -42,6 +40,7 @@ def test_transform(testcase: DataDrivenTestCase) -> None: 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 +49,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 22ef4272e933f..4933bd3522a0f 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 ee0256e2057a3..b1f21b3be79bd 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 @@ -34,6 +51,9 @@ has_recursive_types, ) +# Solving the import cycle: +import mypy.expandtype # ruff: isort: skip + class TypesSuite(Suite): def setUp(self) -> None: @@ -110,16 +130,35 @@ 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?]") + 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 +168,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 +204,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 +269,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 +636,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 +655,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 +900,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) @@ -1138,7 +1179,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) @@ -1287,3 +1327,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 = 8 + + @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/typefixture.py b/mypy/test/typefixture.py index d12e7abab0e25..bf1500a3cdec7 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) @@ -208,7 +220,14 @@ def make_type_var( 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) + return TypeVarTupleType( + name, + name, + id, + upper_bound, + self.std_tuple, + AnyType(TypeOfAny.from_omitted_generics), + ) 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) @@ -297,13 +316,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.o, + self.std_tuple, + 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 0000000000000..2d66752f61bd7 --- /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 771119dbdc706..2b748ec1bdc4a 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 038d948522f0d..2fcc376cfb7cc 100644 --- a/mypy/traverser.py +++ b/mypy/traverser.py @@ -873,6 +873,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 +937,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 535f50d5cf5e1..bb34d8de28844 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 9b432d8e68ec5..c7a653a1552df 100644 --- a/mypy/tvar_scope.py +++ b/mypy/tvar_scope.py @@ -90,11 +90,12 @@ def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType: 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 +107,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 +118,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 5a5643f35c017..cbfa43a77b812 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]) 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]) @@ -480,13 +479,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]) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index f3329af6207af..d894e2cc8c514 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -4,8 +4,8 @@ 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 @@ -81,12 +81,12 @@ UninhabitedType, UnionType, UnpackType, - bad_type_type_item, callable_with_ellipsis, flatten_nested_unions, get_proper_type, has_type_vars, ) +from mypy.types_utils import is_bad_type_type_item from mypy.typetraverser import TypeTraverserVisitor from mypy.typevars import fill_typevars @@ -321,6 +321,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, ) @@ -343,16 +344,7 @@ 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 @@ -366,6 +358,8 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) return AnyType(TypeOfAny.from_error) if isinstance(sym.node, TypeVarTupleExpr): if tvar_def is None: + if self.allow_unbound_tvars: + return t self.fail(f'TypeVarTuple "{t.name}" is unbound', t, code=codes.VALID_TYPE) return AnyType(TypeOfAny.from_error) assert isinstance(tvar_def, TypeVarTupleType) @@ -381,6 +375,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, ) @@ -398,28 +393,31 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) 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 + and not res.type.has_type_var_tuple_type ): 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, ) @@ -563,7 +561,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) @@ -654,9 +652,7 @@ 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 @@ -690,8 +686,13 @@ def analyze_type_with_type_info( 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 + if instance.args: + # -1 to account for empty tuple + valid_arg_length = len(instance.args) >= len(info.type_vars) - 1 + # Empty case is special cased and we want to infer a Tuple[Any, ...] + # instead of the empty tuple, so no - 1 here. + else: + valid_arg_length = False else: valid_arg_length = len(instance.args) == len(info.type_vars) @@ -702,7 +703,7 @@ def analyze_type_with_type_info( 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,13 +711,14 @@ 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.fail, False, ctx, + self.options, use_standard_error=True, ) return tup.copy_modified(items=self.anal_array(tup.items), fallback=instance) @@ -725,13 +727,14 @@ def analyze_type_with_type_info( # 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.fail, False, ctx, + self.options, use_standard_error=True, ) # Create a named TypedDictType @@ -935,8 +938,15 @@ def visit_callable_type(self, t: CallableType, nested: bool = True) -> Type: ] else: arg_types = self.anal_array(t.arg_types, nested=nested) + # 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 @@ -1109,7 +1119,13 @@ 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 @@ -1240,7 +1256,7 @@ 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) @@ -1260,7 +1276,6 @@ def analyze_callable_args( args: list[Type] = [] kinds: list[ArgKind] = [] names: list[str | None] = [] - found_unpack = False for arg in arglist.items: if isinstance(arg, CallableArgument): args.append(arg.typ) @@ -1286,10 +1301,7 @@ def analyze_callable_args( # 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 + if sym.fullname in ("typing_extensions.Unpack", "typing.Unpack"): kind = ARG_STAR args.append(arg) kinds.append(kind) @@ -1534,13 +1546,15 @@ def anal_type(self, t: Type, nested: bool = True, *, allow_param_spec: bool = Fa 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 +1583,9 @@ 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): + if isinstance(item, UnpackType) and not isinstance( + get_proper_type(item.type), TupleType + ): if not num_unpacks: new_items.append(item) num_unpacks += 1 @@ -1600,12 +1616,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 +1633,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 +1646,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,12 +1668,24 @@ def get_omitted_any( return any_type +def fix_type_var_tuple_argument(any_type: Type, 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, [any_type]) + ) + 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: @@ -1667,10 +1698,10 @@ def fix_instance( fullname: str | None = None else: fullname = t.type.fullname - any_type = get_omitted_any( - disallow_any, fail, note, t, python_version, fullname, unexpanded_type - ) + any_type = get_omitted_any(disallow_any, fail, note, t, options, fullname, unexpanded_type) t.args = (any_type,) * len(t.type.type_vars) + fix_type_var_tuple_argument(any_type, t) + return # Invalid number of type parameters. fail( @@ -1682,25 +1713,30 @@ def fix_instance( # 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) + fix_type_var_tuple_argument(AnyType(TypeOfAny.from_error), t) t.invalid = True -def expand_type_alias( +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 @@ -1708,12 +1744,17 @@ def expand_type_alias( """ exp_len = len(node.alias_tvars) act_len = len(args) - if exp_len > 0 and act_len == 0: + if ( + exp_len > 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, ctx.line, ctx.column, + options, disallow_any=disallow_any, fail=fail, unexpanded_type=unexpanded_type, @@ -1735,7 +1776,7 @@ def expand_type_alias( tp.line = ctx.line tp.column = ctx.column return tp - if act_len != exp_len: + if act_len != exp_len and node.tvar_tuple_index is None: if use_standard_error: # This is used if type alias is an internal representation of another type, # for example a generic TypedDict or NamedTuple. @@ -1743,7 +1784,7 @@ def expand_type_alias( 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) + 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. @@ -1764,12 +1805,13 @@ def set_any_tvars( node: TypeAlias, newline: int, newcolumn: int, + options: Options, *, from_error: bool = False, disallow_any: 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 else: @@ -1780,7 +1822,7 @@ def set_any_tvars( 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 @@ -1791,20 +1833,14 @@ def set_any_tvars( 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 + args: list[Type] = [] + for tv in node.alias_tvars: + if isinstance(tv, TypeVarTupleType): + args.append(UnpackType(Instance(tv.tuple_fallback.type, [any_type]))) + else: + args.append(any_type) + return TypeAliasType(node, args, newline, newcolumn) def flatten_tvars(lists: list[list[T]]) -> list[T]: @@ -1896,7 +1932,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,34 +2063,30 @@ 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: +def fix_instance_types(t: Type, fail: MsgCallback, note: MsgCallback, options: Options) -> 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)) + t.accept(InstanceFixer(fail, note, options)) class InstanceFixer(TypeTraverserVisitor): - def __init__( - self, fail: MsgCallback, note: MsgCallback, python_version: tuple[int, int] - ) -> None: + def __init__(self, fail: MsgCallback, note: MsgCallback, options: Options) -> None: self.fail = fail self.note = note - self.python_version = python_version + self.options = options def visit_instance(self, typ: Instance) -> None: super().visit_instance(typ) - if len(typ.args) != len(typ.type.type_vars): + if len(typ.args) != len(typ.type.type_vars) and not typ.type.has_type_var_tuple_type: fix_instance( typ, self.fail, self.note, disallow_any=False, - python_version=self.python_version, + options=self.options, use_generic_error=True, ) diff --git a/mypy/typeops.py b/mypy/typeops.py index 8c01fb1180765..519d3de995f57 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -76,12 +76,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 @@ -103,6 +109,11 @@ def tuple_fallback(typ: TupleType) -> Instance: # TODO: might make sense to do recursion here to support nested unpacks # of tuple constants items.extend(unpacked_type.items) + elif ( + isinstance(unpacked_type, Instance) + and unpacked_type.type.fullname == "builtins.tuple" + ): + items.append(unpacked_type.args[0]) else: raise NotImplementedError else: @@ -385,25 +396,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 +406,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,68 +491,80 @@ 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) + + items = new_items + if len(items) <= 1: + break + items.reverse() - return [items[i] for i in range(len(items)) if i not in removed] + return items def _get_type_special_method_bool_ret_type(t: Type) -> Type | None: @@ -707,7 +710,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) @@ -992,17 +995,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 +1042,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 9858559ad5c1c..ba629a3553cfb 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 @@ -151,12 +151,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 +190,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? @@ -256,7 +263,10 @@ def accept(self, visitor: TypeVisitor[T]) -> T: raise RuntimeError("Not implemented") 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 +321,26 @@ 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 - ) + + 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. @@ -505,13 +532,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 +546,7 @@ def __init__( fullname: str, id: TypeVarId | int, upper_bound: Type, + default: Type, line: int = -1, column: int = -1, ) -> None: @@ -529,6 +557,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 +566,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 +594,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 +652,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 +660,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 +709,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 +726,7 @@ def with_flavor(self, flavor: int) -> ParamSpecType: self.id, flavor, upper_bound=self.upper_bound, + default=self.default, prefix=self.prefix, ) @@ -696,6 +736,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 +745,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 +780,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 +793,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"]), ) @@ -766,11 +811,12 @@ def __init__( id: TypeVarId | int, upper_bound: Type, tuple_fallback: Instance, + default: Type, *, line: int = -1, column: int = -1, ) -> 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 def serialize(self) -> JsonDict: @@ -782,6 +828,7 @@ 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(), } @classmethod @@ -793,6 +840,7 @@ def deserialize(cls, data: JsonDict) -> TypeVarTupleType: data["id"], deserialize_type(data["upper_bound"]), Instance.deserialize(data["tuple_fallback"]), + deserialize_type(data["default"]), ) def accept(self, visitor: TypeVisitor[T]) -> T: @@ -806,18 +854,21 @@ def __eq__(self, other: object) -> bool: 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) - - 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, + **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, ) @@ -950,7 +1001,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 +1027,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" @@ -1013,6 +1066,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'.""" @@ -1429,9 +1488,6 @@ 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? @@ -1661,7 +1717,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 @@ -1775,7 +1831,7 @@ 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, @@ -1976,20 +2032,15 @@ 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, - ) + + return arg_type.copy_modified(flavor=ParamSpecFlavor.BARE, prefix=prefix) def expand_param_spec( self, c: CallableType | Parameters, no_prefix: bool = False @@ -2388,6 +2439,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 +2450,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 +2464,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) @@ -2489,7 +2544,8 @@ def simple_name(self) -> str: 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 serialize(self) -> JsonDict: assert False, "Synthetic types don't serialize" @@ -2657,18 +2713,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 +2780,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 +2890,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))) @@ -2921,7 +2967,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 ( # noqa: F811 ALL_STRATEGY as ALL_STRATEGY, ANY_STRATEGY as ANY_STRATEGY, BoolTypeQuery as BoolTypeQuery, @@ -2945,9 +2991,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 + "?" @@ -2989,7 +3036,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 "" @@ -3012,6 +3059,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 +3076,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 +3116,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: @@ -3101,6 +3154,8 @@ 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})" @@ -3119,12 +3174,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}" @@ -3137,11 +3198,12 @@ def visit_overloaded(self, t: Overloaded) -> str: def visit_tuple_type(self, t: TupleType) -> str: s = self.list_str(t.items) + 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: @@ -3247,18 +3309,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 +3317,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 +3329,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 +3367,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 +3432,25 @@ 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 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 +3464,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 +3485,33 @@ 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 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", [])]), - ) - 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() +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: - 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: + if 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()] + all_types.add(t) + return new_types + + +# 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 0000000000000..43bca05d6bf9d --- /dev/null +++ b/mypy/types_utils.py @@ -0,0 +1,160 @@ +""" +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] (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): + return False + return any(is_invalid_recursive_alias(seen_nodes, item) for item in target.items) + + +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_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 + + +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: + 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 bd1abd2048852..49433e346765e 100644 --- a/mypy/typeshed/stdlib/VERSIONS +++ b/mypy/typeshed/stdlib/VERSIONS @@ -61,7 +61,7 @@ antigravity: 2.7- argparse: 2.7- array: 2.7- ast: 2.7- -asynchat: 2.7- +asynchat: 2.7-3.11 asyncio: 3.4- asyncio.mixins: 3.10- asyncio.exceptions: 3.8- @@ -72,7 +72,7 @@ asyncio.taskgroups: 3.11- asyncio.threads: 3.9- asyncio.timeouts: 3.11- asyncio.trsock: 3.8- -asyncore: 2.7- +asyncore: 2.7-3.11 atexit: 2.7- audioop: 2.7- base64: 2.7- @@ -112,7 +112,7 @@ dbm: 2.7- decimal: 2.7- difflib: 2.7- dis: 2.7- -distutils: 2.7- +distutils: 2.7-3.11 distutils.command.bdist_msi: 2.7-3.10 distutils.command.bdist_wininst: 2.7-3.9 doctest: 2.7- @@ -147,11 +147,12 @@ html: 3.0- http: 3.0- imaplib: 2.7- imghdr: 2.7- -imp: 2.7- +imp: 2.7-3.11 importlib: 2.7- importlib.metadata: 3.8- importlib.metadata._meta: 3.10- importlib.resources: 3.7- +importlib.resources.abc: 3.11- inspect: 2.7- io: 2.7- ipaddress: 3.3- @@ -227,7 +228,7 @@ shlex: 2.7- shutil: 2.7- signal: 2.7- site: 2.7- -smtpd: 2.7- +smtpd: 2.7-3.11 smtplib: 2.7- sndhdr: 2.7- socket: 2.7- diff --git a/mypy/typeshed/stdlib/_ast.pyi b/mypy/typeshed/stdlib/_ast.pyi index 7bc47266d713e..05e2a08fdc885 100644 --- a/mypy/typeshed/stdlib/_ast.pyi +++ b/mypy/typeshed/stdlib/_ast.pyi @@ -1,13 +1,14 @@ import sys +import typing_extensions from typing import Any, ClassVar -from typing_extensions import Literal, TypeAlias +from typing_extensions import Literal PyCF_ONLY_AST: Literal[1024] if sys.version_info >= (3, 8): PyCF_TYPE_COMMENTS: Literal[4096] PyCF_ALLOW_TOP_LEVEL_AWAIT: Literal[8192] -_Identifier: TypeAlias = str +_Identifier: typing_extensions.TypeAlias = str class AST: if sys.version_info >= (3, 10): @@ -59,31 +60,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): @@ -366,10 +379,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): @@ -526,7 +539,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") @@ -571,3 +584,25 @@ 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): ... + + 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", "typeparams", "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 4c79eec14d729..58488e3d15afe 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/_collections_abc.pyi b/mypy/typeshed/stdlib/_collections_abc.pyi index 352da6cfb3319..ba2f638d81c93 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,11 +24,13 @@ 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, + runtime_checkable, ) from typing_extensions import final @@ -58,6 +61,8 @@ __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. @@ -79,3 +84,9 @@ class dict_items(ItemsView[_KT_co, _VT_co], Generic[_KT_co, _VT_co]): # undocum 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 817f251586b21..24e11261140b5 100644 --- a/mypy/typeshed/stdlib/_compression.pyi +++ b/mypy/typeshed/stdlib/_compression.pyi @@ -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 7e9b9e4e7a799..19ea487e15302 100644 --- a/mypy/typeshed/stdlib/_csv.pyi +++ b/mypy/typeshed/stdlib/_csv.pyi @@ -1,3 +1,4 @@ +import sys from _typeshed import SupportsWrite from collections.abc import Iterable, Iterator from typing import Any @@ -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 0ad2fcb571b80..756ee86d33429 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 +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,152 @@ 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: ... + +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: ... + def __buffer__(self, __flags: int) -> memoryview: ... + def __release_buffer__(self, __buffer: memoryview) -> None: ... + +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 _CanCastTo(_CData): ... +class _PointerLike(_CanCastTo): ... + +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, __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[[type[_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 + _flags_: ClassVar[int] # Abstract attribute that must be defined on subclasses + @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: ... + +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 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, __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: ... + +class ArgumentError(Exception): ... + +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/_decimal.pyi b/mypy/typeshed/stdlib/_decimal.pyi index b8208fe180a12..60c6094569545 100644 --- a/mypy/typeshed/stdlib/_decimal.pyi +++ b/mypy/typeshed/stdlib/_decimal.pyi @@ -77,28 +77,28 @@ class Decimal: 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: ... @@ -116,7 +116,7 @@ class 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: ... diff --git a/mypy/typeshed/stdlib/_dummy_thread.pyi b/mypy/typeshed/stdlib/_dummy_thread.pyi index e371dd0e9933a..541096734a914 100644 --- a/mypy/typeshed/stdlib/_dummy_thread.pyi +++ b/mypy/typeshed/stdlib/_dummy_thread.pyi @@ -7,7 +7,7 @@ __all__ = ["error", "start_new_thread", "exit", "get_ident", "allocate_lock", "i TIMEOUT_MAX: int error = RuntimeError -def start_new_thread(function: Callable[..., object], args: tuple[Any, ...], kwargs: dict[str, Any] = ...) -> None: ... +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 9a49dfa9649ef..abcf3a13a496f 100644 --- a/mypy/typeshed/stdlib/_dummy_threading.pyi +++ b/mypy/typeshed/stdlib/_dummy_threading.pyi @@ -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, diff --git a/mypy/typeshed/stdlib/_sitebuiltins.pyi b/mypy/typeshed/stdlib/_sitebuiltins.pyi index 3bda2d88425d2..94ad701d4a730 100644 --- a/mypy/typeshed/stdlib/_sitebuiltins.pyi +++ b/mypy/typeshed/stdlib/_sitebuiltins.pyi @@ -10,7 +10,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 f7b0e6901bf4c..7a0ede62838c1 100644 --- a/mypy/typeshed/stdlib/_socket.pyi +++ b/mypy/typeshed/stdlib/_socket.pyi @@ -692,3 +692,28 @@ 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: ... + +if sys.version_info >= (3, 12): + IP_PKTINFO: int + IP_UNBLOCK_SOURCE: int + IP_BLOCK_SOURCE: int + IP_ADD_SOURCE_MEMBERSHIP: int + IP_DROP_SOURCE_MEMBERSHIP: int + if sys.platform == "win32": + 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 + else: + ETHERTYPE_ARP: int + ETHERTYPE_IP: int + ETHERTYPE_IPV6: int + ETHERTYPE_VLAN: int diff --git a/mypy/typeshed/stdlib/_thread.pyi b/mypy/typeshed/stdlib/_thread.pyi index 152362edcaea9..dba8664fbf132 100644 --- a/mypy/typeshed/stdlib/_thread.pyi +++ b/mypy/typeshed/stdlib/_thread.pyi @@ -43,3 +43,6 @@ if sys.version_info >= (3, 8): @property def thread(self) -> Thread | None: ... _excepthook: Callable[[_ExceptHookArgs], Any] + +if sys.version_info >= (3, 12): + def daemon_threads_allowed() -> bool: ... diff --git a/mypy/typeshed/stdlib/_tkinter.pyi b/mypy/typeshed/stdlib/_tkinter.pyi index 271fd37df68be..89610e21d9e71 100644 --- a/mypy/typeshed/stdlib/_tkinter.pyi +++ b/mypy/typeshed/stdlib/_tkinter.pyi @@ -22,12 +22,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): ... diff --git a/mypy/typeshed/stdlib/_typeshed/__init__.pyi b/mypy/typeshed/stdlib/_typeshed/__init__.pyi index d0c6b3ab1173d..5d03142c6d71f 100644 --- a/mypy/typeshed/stdlib/_typeshed/__init__.pyi +++ b/mypy/typeshed/stdlib/_typeshed/__init__.pyi @@ -2,17 +2,13 @@ # # 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, Generic, Protocol, TypeVar, overload +from typing_extensions import Buffer, Final, Literal, LiteralString, TypeAlias, final _KT = TypeVar("_KT") _KT_co = TypeVar("_KT_co", covariant=True) @@ -227,42 +223,33 @@ class SupportsNoArgReadline(Protocol[_T_co]): class SupportsWrite(Protocol[_T_contra]): def write(self, __s: _T_contra) -> object: ... -ReadOnlyBuffer: TypeAlias = bytes # stable +# 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 +280,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 diff --git a/mypy/typeshed/stdlib/_weakref.pyi b/mypy/typeshed/stdlib/_weakref.pyi index 2a43de3ffd6b4..b6044fac46284 100644 --- a/mypy/typeshed/stdlib/_weakref.pyi +++ b/mypy/typeshed/stdlib/_weakref.pyi @@ -20,7 +20,7 @@ class ProxyType(Generic[_T]): # "weakproxy" 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: ... if sys.version_info >= (3, 9): def __class_getitem__(cls, item: Any) -> GenericAlias: ... diff --git a/mypy/typeshed/stdlib/_winapi.pyi b/mypy/typeshed/stdlib/_winapi.pyi index e21402b801c55..b51d844701ac4 100644 --- a/mypy/typeshed/stdlib/_winapi.pyi +++ b/mypy/typeshed/stdlib/_winapi.pyi @@ -54,7 +54,10 @@ if sys.platform == "win32": 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] @@ -134,6 +137,34 @@ if sys.platform == "win32": LCMAP_TRADITIONAL_CHINESE: int LCMAP_UPPERCASE: int + 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: ... @@ -221,3 +252,6 @@ if sys.platform == "win32": 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: ... diff --git a/mypy/typeshed/stdlib/abc.pyi b/mypy/typeshed/stdlib/abc.pyi index 068dab4752be7..ec04d8f85d127 100644 --- a/mypy/typeshed/stdlib/abc.pyi +++ b/mypy/typeshed/stdlib/abc.pyi @@ -2,12 +2,13 @@ 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, TypeVar +from typing_extensions import Concatenate, Literal, ParamSpec _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): @@ -28,13 +29,13 @@ class ABCMeta(type): def abstractmethod(funcobj: _FuncT) -> _FuncT: ... -class abstractclassmethod(classmethod[_R_co], Generic[_R_co]): +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[_T, _P], _R_co]) -> None: ... -class abstractstaticmethod(staticmethod[_R_co], Generic[_R_co]): +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: ... class abstractproperty(property): __isabstractmethod__: Literal[True] diff --git a/mypy/typeshed/stdlib/argparse.pyi b/mypy/typeshed/stdlib/argparse.pyi index 20d9dfa9d1376..e41048516dd9a 100644 --- a/mypy/typeshed/stdlib/argparse.pyi +++ b/mypy/typeshed/stdlib/argparse.pyi @@ -2,7 +2,7 @@ import sys 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_extensions import Literal, Self, TypeAlias __all__ = [ "ArgumentParser", @@ -97,8 +97,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: ... @@ -135,7 +143,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 +160,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 +169,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: Namespace | None = None) -> Namespace: ... # type: ignore[misc] @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 +209,27 @@ 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: Namespace | None = None) -> tuple[Namespace, list[str]]: ... # type: ignore[misc] + @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: ... - def parse_known_intermixed_args( - self, args: Sequence[str] | None = None, namespace: Namespace | None = None - ) -> tuple[Namespace, list[str]]: ... + @overload + def parse_intermixed_args(self, args: Sequence[str] | None = None, namespace: Namespace | None = None) -> Namespace: ... # type: ignore[misc] + @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) -> tuple[Namespace, list[str]]: ... # type: ignore[misc] + @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 +255,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 +276,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: ... @@ -347,7 +369,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 diff --git a/mypy/typeshed/stdlib/array.pyi b/mypy/typeshed/stdlib/array.pyi index 827bbb97897f9..8b003503bc9ba 100644 --- a/mypy/typeshed/stdlib/array.pyi +++ b/mypy/typeshed/stdlib/array.pyi @@ -61,24 +61,26 @@ class array(MutableSequence[_T], Generic[_T]): 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 __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 __buffer__(self, __flags: int) -> memoryview: ... + def __release_buffer__(self, __buffer: memoryview) -> None: ... ArrayType = array diff --git a/mypy/typeshed/stdlib/ast.pyi b/mypy/typeshed/stdlib/ast.pyi index ea899e150f97f..3771381413405 100644 --- a/mypy/typeshed/stdlib/ast.pyi +++ b/mypy/typeshed/stdlib/ast.pyi @@ -3,7 +3,7 @@ import sys from _ast import * from _typeshed import ReadableBuffer, Unused from collections.abc import Iterator -from typing import Any, TypeVar, overload +from typing import Any, TypeVar as _TypeVar, overload from typing_extensions import Literal if sys.version_info >= (3, 8): @@ -168,7 +168,7 @@ 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): @overload diff --git a/mypy/typeshed/stdlib/asyncio/__init__.pyi b/mypy/typeshed/stdlib/asyncio/__init__.pyi index 4afcd37f5d4a5..c114651843899 100644 --- a/mypy/typeshed/stdlib/asyncio/__init__.pyi +++ b/mypy/typeshed/stdlib/asyncio/__init__.pyi @@ -1,4 +1,7 @@ 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 * @@ -28,3 +31,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 3b8f286710b96..9924f728f6eae 100644 --- a/mypy/typeshed/stdlib/asyncio/base_events.pyi +++ b/mypy/typeshed/stdlib/asyncio/base_events.pyi @@ -1,12 +1,13 @@ 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 @@ -63,11 +64,7 @@ class Server(AbstractServer): 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: ... @@ -86,13 +83,11 @@ class BaseEventLoop(AbstractEventLoop): 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]: ... + def create_task(self, coro: _CoroutineLike[_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) -> 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]) -> Task[_T]: ... def set_task_factory(self, factory: _TaskFactory | None) -> None: ... def get_task_factory(self) -> _TaskFactory | None: ... @@ -268,7 +263,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 +317,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], @@ -417,9 +412,9 @@ 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 remove_signal_handler(self, sig: int) -> bool: ... diff --git a/mypy/typeshed/stdlib/asyncio/base_subprocess.pyi b/mypy/typeshed/stdlib/asyncio/base_subprocess.pyi index 597c8302988ea..8f262cd5c760b 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 diff --git a/mypy/typeshed/stdlib/asyncio/events.pyi b/mypy/typeshed/stdlib/asyncio/events.pyi index f97afe873c9fd..2054f6e522a14 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 . import _AwaitableLike, _CoroutineLike from .base_events import Server from .futures import Future from .protocols import BaseProtocol @@ -112,13 +113,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 @@ -158,20 +154,14 @@ 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, + self, coro: _CoroutineLike[_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 - ) -> Task[_T]: ... + def create_task(self, coro: _CoroutineLike[_T], *, name: str | 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]) -> Task[_T]: ... @abstractmethod def set_task_factory(self, factory: _TaskFactory | None) -> None: ... @@ -368,7 +358,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 +418,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, @@ -569,11 +559,11 @@ 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: ... diff --git a/mypy/typeshed/stdlib/asyncio/sslproto.pyi b/mypy/typeshed/stdlib/asyncio/sslproto.pyi index aadc7d32b40f3..09733e5f9a018 100644 --- a/mypy/typeshed/stdlib/asyncio/sslproto.pyi +++ b/mypy/typeshed/stdlib/asyncio/sslproto.pyi @@ -66,7 +66,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]: ... diff --git a/mypy/typeshed/stdlib/asyncio/subprocess.pyi b/mypy/typeshed/stdlib/asyncio/subprocess.pyi index 10a414f245375..b8877b3605274 100644 --- a/mypy/typeshed/stdlib/asyncio/subprocess.pyi +++ b/mypy/typeshed/stdlib/asyncio/subprocess.pyi @@ -54,24 +54,24 @@ 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, @@ -87,23 +87,23 @@ if sys.version_info >= (3, 11): encoding: None = None, errors: 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 = ..., + text: bool | None = None, + 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,23 +120,23 @@ 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, @@ -152,22 +152,22 @@ elif sys.version_info >= (3, 10): encoding: None = None, errors: 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 = ..., + text: bool | None = None, + 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,22 +185,22 @@ 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, @@ -217,19 +217,19 @@ else: # >= 3.9 encoding: None = None, errors: 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 = ..., + text: bool | None = None, + 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 8daa96f1ede0e..08ea8f66559c0 100644 --- a/mypy/typeshed/stdlib/asyncio/taskgroups.pyi +++ b/mypy/typeshed/stdlib/asyncio/taskgroups.pyi @@ -1,11 +1,11 @@ # This only exists in 3.11+. See VERSIONS. -from collections.abc import Coroutine, Generator from contextvars import Context from types import TracebackType -from typing import Any, TypeVar +from typing import TypeVar from typing_extensions import Self +from . import _CoroutineLike from .tasks import Task __all__ = ["TaskGroup"] @@ -15,6 +15,4 @@ _T = TypeVar("_T") class TaskGroup: 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]: ... diff --git a/mypy/typeshed/stdlib/asyncio/tasks.pyi b/mypy/typeshed/stdlib/asyncio/tasks.pyi index 0a44255a3ac84..d8c101f281fc0 100644 --- a/mypy/typeshed/stdlib/asyncio/tasks.pyi +++ b/mypy/typeshed/stdlib/asyncio/tasks.pyi @@ -5,6 +5,7 @@ from types import FrameType from typing import Any, Generic, TextIO, TypeVar, overload from typing_extensions import Literal, TypeAlias +from . import _CoroutineLike from .events import AbstractEventLoop from .futures import Future @@ -140,7 +141,7 @@ if sys.version_info >= (3, 10): 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(*coros_or_futures: _FutureLike[Any], return_exceptions: bool = False) -> Future[list[Any]]: ... else: @overload @@ -230,7 +231,7 @@ else: tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException, _T5 | BaseException] ]: ... @overload - def gather( # type: ignore[misc] + def gather( *coros_or_futures: _FutureLike[Any], loop: AbstractEventLoop | None = None, return_exceptions: bool = False ) -> Future[list[Any]]: ... @@ -274,6 +275,11 @@ else: ) -> 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] +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, @@ -281,18 +287,12 @@ else: class Task(Future[_T_co], Generic[_T_co]): # type: ignore[type-var] # pyright: ignore[reportGeneralTypeIssues] if sys.version_info >= (3, 8): def __init__( - self, - coro: Generator[_TaskYieldType, None, _T_co] | Awaitable[_T_co], - *, - loop: AbstractEventLoop = ..., - name: str | None = ..., + self, coro: _TaskCompatibleCoro[_T_co], *, loop: AbstractEventLoop = ..., name: str | None = ... ) -> None: ... else: - def __init__( - self, coro: Generator[_TaskYieldType, None, _T_co] | Awaitable[_T_co], *, loop: AbstractEventLoop = ... - ) -> None: ... + def __init__(self, coro: _TaskCompatibleCoro[_T_co], *, loop: AbstractEventLoop = ...) -> None: ... if sys.version_info >= (3, 8): - def get_coro(self) -> Generator[_TaskYieldType, None, _T_co] | Awaitable[_T_co]: ... + def get_coro(self) -> _TaskCompatibleCoro[_T_co]: ... def get_name(self) -> str: ... def set_name(self, __value: object) -> None: ... @@ -312,15 +312,13 @@ 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]: ... + def create_task(coro: _CoroutineLike[_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) -> Task[_T]: ... else: - def create_task(coro: Generator[Any, None, _T] | Coroutine[Any, Any, _T]) -> Task[_T]: ... + def create_task(coro: _CoroutineLike[_T]) -> Task[_T]: ... def current_task(loop: AbstractEventLoop | None = None) -> Task[Any] | None: ... def _enter_task(loop: AbstractEventLoop, task: Task[Any]) -> None: ... diff --git a/mypy/typeshed/stdlib/asyncio/windows_utils.pyi b/mypy/typeshed/stdlib/asyncio/windows_utils.pyi index f3a82e2b8462b..9f88718b7b70d 100644 --- a/mypy/typeshed/stdlib/asyncio/windows_utils.pyi +++ b/mypy/typeshed/stdlib/asyncio/windows_utils.pyi @@ -15,7 +15,7 @@ if sys.platform == "win32": 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: ... diff --git a/mypy/typeshed/stdlib/binhex.pyi b/mypy/typeshed/stdlib/binhex.pyi index e0993c840ce74..64ba9f6150b43 100644 --- a/mypy/typeshed/stdlib/binhex.pyi +++ b/mypy/typeshed/stdlib/binhex.pyi @@ -1,4 +1,4 @@ -from _typeshed import _BufferWithLen +from _typeshed import SizedBuffer from typing import IO, Any from typing_extensions import Literal, TypeAlias @@ -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 7b8e25084c917..7415a1b7680d5 100644 --- a/mypy/typeshed/stdlib/builtins.pyi +++ b/mypy/typeshed/stdlib/builtins.pyi @@ -36,7 +36,6 @@ from typing import ( # noqa: Y022 IO, Any, BinaryIO, - ByteString, ClassVar, Generic, Mapping, @@ -54,7 +53,17 @@ from typing import ( # noqa: Y022 overload, type_check_only, ) -from typing_extensions import Literal, Self, SupportsIndex, TypeAlias, TypeGuard, final +from typing_extensions import ( + Concatenate, + Literal, + ParamSpec, + Self, + SupportsIndex, + TypeAlias, + TypeGuard, + TypeVarTuple, + final, +) if sys.version_info >= (3, 9): from types import GenericAlias @@ -75,6 +84,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 @@ -92,8 +102,8 @@ class object: # 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 __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: ... @@ -107,36 +117,40 @@ class object: def __reduce_ex__(self, __protocol: SupportsIndex) -> str | tuple[Any, ...]: ... else: def __reduce_ex__(self, __protocol: int) -> 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: ... + 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[_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[_T, _P], _R_co]) -> None: ... + 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 __wrapped__(self) -> Callable[Concatenate[_T, _P], _R_co]: ... class type: @property @@ -181,8 +195,10 @@ class type: @classmethod def __prepare__(metacls, __name: str, __bases: tuple[type, ...], **kwds: Any) -> Mapping[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 @@ -240,45 +256,48 @@ 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]: ... + 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, __x: Literal[0], __modulo: None) -> Literal[1]: ... + def __pow__(self, __value: Literal[0], __mod: None) -> Literal[1]: ... @overload - def __pow__(self, __x: _PositiveInteger, __modulo: None = None) -> int: ... + def __pow__(self, __value: _PositiveInteger, __mod: None = None) -> int: ... @overload - def __pow__(self, __x: _NegativeInteger, __modulo: None = None) -> float: ... + def __pow__(self, __value: _NegativeInteger, __mod: None = None) -> float: ... # positive x -> int; negative x -> 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: ... @@ -287,12 +306,12 @@ class int: def __floor__(self) -> 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: ... @@ -305,39 +324,39 @@ class float: 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: ... + 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 x -> float; negative x -> 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): @@ -348,12 +367,12 @@ class float: 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 __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: ... @@ -383,18 +402,18 @@ class complex: @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: ... @@ -427,7 +446,7 @@ class str(Sequence[str]): 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 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 isalnum(self) -> bool: ... @@ -478,24 +497,24 @@ class str(Sequence[str]): @staticmethod @overload def maketrans(__x: str, __y: str, __z: str) -> dict[int, int | None]: ... - def __add__(self, __s: str) -> str: ... # type: ignore[misc] + 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 __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: ... @overload @@ -573,32 +592,34 @@ class bytes(ByteString): def upper(self) -> 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 __len__(self) -> int: ... def __iter__(self) -> Iterator[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 __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 @@ -689,30 +710,32 @@ class bytearray(MutableSequence[int], ByteString): 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 __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]): @@ -747,16 +770,16 @@ class memoryview(Sequence[int]): ) -> None: ... def cast(self, format: str, shape: list[int] | tuple[int, ...] = ...) -> memoryview: ... @overload - def __getitem__(self, __i: SupportsIndex) -> int: ... + def __getitem__(self, __key: SupportsIndex) -> int: ... @overload - def __getitem__(self, __s: slice) -> memoryview: ... + def __getitem__(self, __key: slice) -> memoryview: ... def __contains__(self, __x: object) -> bool: ... def __iter__(self) -> Iterator[int]: ... def __len__(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, __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): @@ -774,35 +797,38 @@ class memoryview(Sequence[int]): else: def hex(self) -> str: ... + def __buffer__(self, __flags: int) -> memoryview: ... + def __release_buffer__(self, __buffer: memoryview) -> None: ... + @final class bool(int): 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]: ... @final @@ -823,22 +849,22 @@ class slice: class tuple(Sequence[_T_co], Generic[_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: ... + 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: ... @overload - def __add__(self, __x: tuple[_T_co, ...]) -> tuple[_T_co, ...]: ... + def __add__(self, __value: 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 __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): @@ -863,10 +889,12 @@ 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]): @overload @@ -900,25 +928,25 @@ class list(MutableSequence[_T], Generic[_T]): @overload 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: ... if sys.version_info >= (3, 9): def __class_getitem__(cls, __item: Any) -> GenericAlias: ... @@ -937,10 +965,12 @@ class dict(MutableMapping[_KT, _VT], Generic[_KT, _VT]): 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) + # 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: ... + @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]: ... @@ -1005,18 +1035,18 @@ class set(MutableSet[_T], Generic[_T]): def __len__(self) -> int: ... 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: ... __hash__: ClassVar[None] # type: ignore[assignment] if sys.version_info >= (3, 9): def __class_getitem__(cls, __item: Any) -> GenericAlias: ... @@ -1037,14 +1067,14 @@ class frozenset(AbstractSet[_T_co], Generic[_T_co]): def __len__(self) -> int: ... 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: ... if sys.version_info >= (3, 9): def __class_getitem__(cls, __item: Any) -> GenericAlias: ... @@ -1070,12 +1100,12 @@ class range(Sequence[int]): 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 __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: @@ -1093,12 +1123,12 @@ class property: 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 __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] @@ -1302,6 +1332,7 @@ def iter(__function: Callable[[], _T | None], __sentinel: None) -> Iterator[_T]: @overload def iter(__function: 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: @@ -1518,11 +1549,11 @@ if sys.version_info >= (3, 8): @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] + def pow(base: int, exp: Literal[0], mod: None = None) -> Literal[1]: ... @overload - def pow(base: int, exp: _PositiveInteger, mod: None = None) -> int: ... # type: ignore[misc] + def pow(base: int, exp: _PositiveInteger, mod: None = None) -> int: ... @overload - def pow(base: int, exp: _NegativeInteger, mod: None = None) -> float: ... # type: ignore[misc] + def pow(base: int, exp: _NegativeInteger, mod: None = None) -> float: ... # 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 @@ -1553,35 +1584,35 @@ if sys.version_info >= (3, 8): else: @overload - def pow(__base: int, __exp: int, __mod: int) -> int: ... + def pow(__x: int, __y: int, __z: int) -> int: ... @overload - def pow(__base: int, __exp: Literal[0], __mod: None = None) -> Literal[1]: ... # type: ignore[misc] + def pow(__x: int, __y: Literal[0], __z: None = None) -> Literal[1]: ... @overload - def pow(__base: int, __exp: _PositiveInteger, __mod: None = None) -> int: ... # type: ignore[misc] + def pow(__x: int, __y: _PositiveInteger, __z: None = None) -> int: ... @overload - def pow(__base: int, __exp: _NegativeInteger, __mod: None = None) -> float: ... # type: ignore[misc] + def pow(__x: int, __y: _NegativeInteger, __z: None = None) -> float: ... @overload - def pow(__base: int, __exp: int, __mod: None = None) -> Any: ... + def pow(__x: int, __y: int, __z: None = None) -> Any: ... @overload - def pow(__base: _PositiveInteger, __exp: float, __mod: None = None) -> float: ... + def pow(__x: _PositiveInteger, __y: float, __z: None = None) -> float: ... @overload - def pow(__base: _NegativeInteger, __exp: float, __mod: None = None) -> complex: ... + def pow(__x: _NegativeInteger, __y: float, __z: None = None) -> complex: ... @overload - def pow(__base: float, __exp: int, __mod: None = None) -> float: ... + def pow(__x: float, __y: int, __z: None = None) -> float: ... @overload - def pow(__base: float, __exp: complex | _SupportsSomeKindOfPow, __mod: None = None) -> Any: ... + def pow(__x: float, __y: complex | _SupportsSomeKindOfPow, __z: None = None) -> Any: ... @overload - def pow(__base: complex, __exp: complex | _SupportsSomeKindOfPow, __mod: None = None) -> complex: ... + def pow(__x: complex, __y: complex | _SupportsSomeKindOfPow, __z: None = None) -> complex: ... @overload - def pow(__base: _SupportsPow2[_E, _T_co], __exp: _E, __mod: None = None) -> _T_co: ... + def pow(__x: _SupportsPow2[_E, _T_co], __y: _E, __z: None = None) -> _T_co: ... @overload - def pow(__base: _SupportsPow3NoneOnly[_E, _T_co], __exp: _E, __mod: None = None) -> _T_co: ... + def pow(__x: _SupportsPow3NoneOnly[_E, _T_co], __y: _E, __z: None = None) -> _T_co: ... @overload - def pow(__base: _SupportsPow3[_E, _M, _T_co], __exp: _E, __mod: _M) -> _T_co: ... + def pow(__x: _SupportsPow3[_E, _M, _T_co], __y: _E, __z: _M) -> _T_co: ... @overload - def pow(__base: _SupportsSomeKindOfPow, __exp: float, __mod: None = None) -> Any: ... + def pow(__x: _SupportsSomeKindOfPow, __y: float, __z: None = None) -> Any: ... @overload - def pow(__base: _SupportsSomeKindOfPow, __exp: complex, __mod: None = None) -> complex: ... + def pow(__x: _SupportsSomeKindOfPow, __y: complex, __z: None = None) -> complex: ... def quit(code: sys._ExitCode = None) -> NoReturn: ... @@ -1743,7 +1774,7 @@ 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: ... @@ -1812,6 +1843,8 @@ 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): ... diff --git a/mypy/typeshed/stdlib/calendar.pyi b/mypy/typeshed/stdlib/calendar.pyi index 255a12d3348ae..3f881393e99f7 100644 --- a/mypy/typeshed/stdlib/calendar.pyi +++ b/mypy/typeshed/stdlib/calendar.pyi @@ -1,4 +1,5 @@ import datetime +import enum import sys from _typeshed import Unused from collections.abc import Iterable, Sequence @@ -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] @@ -134,12 +152,55 @@ 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/codecs.pyi b/mypy/typeshed/stdlib/codecs.pyi index 5a22853b6aeed..c9b6a4a82da63 100644 --- a/mypy/typeshed/stdlib/codecs.pyi +++ b/mypy/typeshed/stdlib/codecs.pyi @@ -96,6 +96,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 @@ -272,8 +273,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 893a289d3cb14..e56baf8b52c95 100644 --- a/mypy/typeshed/stdlib/collections/__init__.pyi +++ b/mypy/typeshed/stdlib/collections/__init__.pyi @@ -1,6 +1,6 @@ import sys from _collections_abc import dict_items, dict_keys, dict_values -from _typeshed import SupportsKeysAndGetItem, SupportsRichComparison, SupportsRichComparisonT +from _typeshed import SupportsItems, SupportsKeysAndGetItem, SupportsRichComparison, SupportsRichComparisonT from typing import Any, Generic, NoReturn, TypeVar, overload from typing_extensions import Self, SupportsIndex, final @@ -62,6 +62,8 @@ class UserDict(MutableMapping[_KT, _VT], Generic[_KT, _VT]): def __init__(self: UserDict[str, _VT], __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[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: ... @@ -236,19 +238,19 @@ class deque(MutableSequence[_T], Generic[_T]): 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: ... if sys.version_info >= (3, 9): def __class_getitem__(cls, __item: Any) -> GenericAlias: ... @@ -297,10 +299,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: ... diff --git a/mypy/typeshed/stdlib/concurrent/futures/_base.pyi b/mypy/typeshed/stdlib/concurrent/futures/_base.pyi index e792cf1a83c0a..eb5ca4e2dd35f 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, 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, NamedTuple, TypeVar +from typing_extensions import Literal, ParamSpec, Self if sys.version_info >= (3, 9): from types import GenericAlias @@ -69,20 +69,9 @@ class Executor: 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]: ... +class DoneAndNotDoneFutures(NamedTuple, Generic[_T]): + done: set[Future[_T]] + not_done: set[Future[_T]] def wait( fs: Iterable[Future[_T]], timeout: float | None = None, return_when: str = "ALL_COMPLETED" diff --git a/mypy/typeshed/stdlib/concurrent/futures/process.pyi b/mypy/typeshed/stdlib/concurrent/futures/process.pyi index 85af2e7f84c7c..000e7a43503ac 100644 --- a/mypy/typeshed/stdlib/concurrent/futures/process.pyi +++ b/mypy/typeshed/stdlib/concurrent/futures/process.pyi @@ -153,9 +153,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 @@ -174,7 +174,7 @@ class ProcessPoolExecutor(Executor): max_workers: int | None = None, mp_context: BaseContext | None = None, initializer: Callable[..., object] | None = None, - initargs: tuple[Any, ...] = ..., + initargs: tuple[Any, ...] = (), *, max_tasks_per_child: int | None = None, ) -> None: ... @@ -184,7 +184,7 @@ class ProcessPoolExecutor(Executor): max_workers: int | None = None, mp_context: BaseContext | None = None, initializer: Callable[..., object] | None = None, - initargs: tuple[Any, ...] = ..., + initargs: tuple[Any, ...] = (), ) -> 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 e43dd3dfa33a1..0b00d524aa3d1 100644 --- a/mypy/typeshed/stdlib/concurrent/futures/thread.pyi +++ b/mypy/typeshed/stdlib/concurrent/futures/thread.pyi @@ -44,16 +44,16 @@ 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]] def __init__( self, max_workers: int | None = None, thread_name_prefix: str = "", initializer: Callable[..., object] | None = None, - initargs: tuple[Any, ...] = ..., + initargs: tuple[Any, ...] = (), ) -> 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 2c5b683857677..6f9f788310d18 100644 --- a/mypy/typeshed/stdlib/configparser.pyi +++ b/mypy/typeshed/stdlib/configparser.pyi @@ -17,7 +17,6 @@ __all__ = [ "ParsingError", "MissingSectionHeaderError", "ConfigParser", - "SafeConfigParser", "RawConfigParser", "Interpolation", "BasicInterpolation", @@ -29,6 +28,9 @@ __all__ = [ "MAX_INTERPOLATION_DEPTH", ] +if sys.version_info < (3, 12): + __all__ += ["SafeConfigParser"] + _Section: TypeAlias = Mapping[str, str] _Parser: TypeAlias = MutableMapping[str, _Section] _ConverterCallback: TypeAlias = Callable[[str], Any] @@ -69,8 +71,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 +87,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 +103,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, diff --git a/mypy/typeshed/stdlib/contextlib.pyi b/mypy/typeshed/stdlib/contextlib.pyi index feb43aabb0393..dc2101dc01f78 100644 --- a/mypy/typeshed/stdlib/contextlib.pyi +++ b/mypy/typeshed/stdlib/contextlib.pyi @@ -64,9 +64,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]]: ... diff --git a/mypy/typeshed/stdlib/contextvars.pyi b/mypy/typeshed/stdlib/contextvars.pyi index 266d96bce6ffd..ef6e2700e6675 100644 --- a/mypy/typeshed/stdlib/contextvars.pyi +++ b/mypy/typeshed/stdlib/contextvars.pyi @@ -22,8 +22,13 @@ class ContextVar(Generic[_T]): def name(self) -> str: ... @overload def get(self) -> _T: ... - @overload - def get(self, default: _D | _T) -> _D | _T: ... + if sys.version_info >= (3, 8): + @overload + def get(self, default: _D | _T) -> _D | _T: ... + else: + @overload + def get(self, __default: _D | _T) -> _D | _T: ... + def set(self, __value: _T) -> Token[_T]: ... def reset(self, __token: Token[_T]) -> None: ... if sys.version_info >= (3, 9): diff --git a/mypy/typeshed/stdlib/copy.pyi b/mypy/typeshed/stdlib/copy.pyi index f68965d3dc91a..8a2dcc508e5d1 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 234b189fb3dbb..139ba7af22089 100644 --- a/mypy/typeshed/stdlib/csv.pyi +++ b/mypy/typeshed/stdlib/csv.pyi @@ -21,6 +21,9 @@ from _csv import ( unregister_dialect as unregister_dialect, writer as writer, ) + +if sys.version_info >= (3, 12): + from _csv import QUOTE_STRINGS as QUOTE_STRINGS, QUOTE_NOTNULL as QUOTE_NOTNULL from _typeshed import SupportsWrite from collections.abc import Collection, Iterable, Iterator, Mapping, Sequence from typing import Any, Generic, TypeVar, overload @@ -57,6 +60,8 @@ __all__ = [ "DictWriter", "unix_dialect", ] +if sys.version_info >= (3, 12): + __all__ += ["QUOTE_STRINGS", "QUOTE_NOTNULL"] _T = TypeVar("_T") @@ -80,14 +85,14 @@ 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: ... @overload def __init__( @@ -98,14 +103,14 @@ 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]: ... @@ -125,14 +130,14 @@ 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: ... diff --git a/mypy/typeshed/stdlib/ctypes/__init__.pyi b/mypy/typeshed/stdlib/ctypes/__init__.pyi index 497e2f7db70b8..b14fb93c81636 100644 --- a/mypy/typeshed/stdlib/ctypes/__init__.pyi +++ b/mypy/typeshed/stdlib/ctypes/__init__.pyi @@ -1,17 +1,43 @@ 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, + ArgumentError as ArgumentError, + 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 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, 9): from types import GenericAlias _T = TypeVar("_T") _DLLT = TypeVar("_DLLT", bound=CDLL) -_CT = TypeVar("_CT", bound=_CData) DEFAULT_MODE: int @@ -65,53 +91,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 +107,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 +116,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 +128,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 +139,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]): @@ -238,65 +181,9 @@ class c_bool(_SimpleCData[bool]): if sys.platform == "win32": class HRESULT(_SimpleCData[int]): ... # TODO undocumented -class py_object(_CanCastTo, _SimpleCData[_T]): ... - -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: ... +if sys.version_info >= (3, 12): + c_time_t: type[c_int32 | c_int64] -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 py_object(_CanCastTo, _SimpleCData[_T]): ... 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: ... diff --git a/mypy/typeshed/stdlib/dataclasses.pyi b/mypy/typeshed/stdlib/dataclasses.pyi index c02aaabe61964..13cffcd70c0e2 100644 --- a/mypy/typeshed/stdlib/dataclasses.pyi +++ b/mypy/typeshed/stdlib/dataclasses.pyi @@ -223,7 +223,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 @@ -247,12 +247,32 @@ class InitVar(Generic[_T], metaclass=_InitVarMeta): @overload def __class_getitem__(cls, type: Any) -> InitVar[Any]: ... -if sys.version_info >= (3, 11): +if sys.version_info >= (3, 12): + def make_dataclass( + cls_name: str, + fields: Iterable[str | tuple[str, type] | tuple[str, type, 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]], *, - bases: tuple[type, ...] = ..., + bases: tuple[type, ...] = (), namespace: dict[str, Any] | None = None, init: bool = True, repr: bool = True, @@ -271,7 +291,7 @@ elif sys.version_info >= (3, 10): cls_name: str, fields: Iterable[str | tuple[str, type] | tuple[str, type, Any]], *, - bases: tuple[type, ...] = ..., + bases: tuple[type, ...] = (), namespace: dict[str, Any] | None = None, init: bool = True, repr: bool = True, @@ -289,7 +309,7 @@ else: cls_name: str, fields: Iterable[str | tuple[str, type] | tuple[str, type, Any]], *, - bases: tuple[type, ...] = ..., + bases: tuple[type, ...] = (), namespace: dict[str, Any] | None = None, init: bool = True, repr: bool = True, diff --git a/mypy/typeshed/stdlib/datetime.pyi b/mypy/typeshed/stdlib/datetime.pyi index 4da5501ce76d0..2bb2264c97b19 100644 --- a/mypy/typeshed/stdlib/datetime.pyi +++ b/mypy/typeshed/stdlib/datetime.pyi @@ -2,7 +2,7 @@ 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_extensions import Literal, Self, SupportsIndex, TypeAlias, final if sys.version_info >= (3, 11): __all__ = ("date", "datetime", "time", "timedelta", "timezone", "tzinfo", "MINYEAR", "MAXYEAR", "UTC") @@ -49,7 +49,7 @@ 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: ... @classmethod @@ -81,30 +81,30 @@ class date: 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: ... + 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: ... if sys.version_info >= (3, 8): - def __add__(self, __other: timedelta) -> Self: ... - def __radd__(self, __other: timedelta) -> Self: ... + def __add__(self, __value: timedelta) -> Self: ... + def __radd__(self, __value: timedelta) -> Self: ... @overload - def __sub__(self, __other: timedelta) -> Self: ... + def __sub__(self, __value: timedelta) -> Self: ... @overload - def __sub__(self, __other: datetime) -> NoReturn: ... + def __sub__(self, __value: datetime) -> NoReturn: ... @overload - def __sub__(self: _D, __other: _D) -> timedelta: ... + def __sub__(self: _D, __value: _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: ... + def __add__(self, __value: timedelta) -> date: ... + def __radd__(self, __value: timedelta) -> date: ... @overload - def __sub__(self, __other: timedelta) -> date: ... + def __sub__(self, __value: timedelta) -> date: ... @overload - def __sub__(self, __other: datetime) -> NoReturn: ... + def __sub__(self, __value: datetime) -> NoReturn: ... @overload - def __sub__(self, __other: date) -> timedelta: ... + def __sub__(self, __value: date) -> timedelta: ... def weekday(self) -> int: ... def isoweekday(self) -> int: ... @@ -119,10 +119,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,10 +139,10 @@ 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 isoformat(self, timespec: str = ...) -> str: ... @classmethod def fromisoformat(cls, __time_string: str) -> Self: ... @@ -160,10 +160,10 @@ class time: 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,29 +193,29 @@ 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 __bool__(self) -> bool: ... class datetime(date): @@ -223,13 +223,13 @@ class datetime(date): 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 = ..., @@ -280,13 +280,13 @@ 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 = ..., @@ -302,20 +302,20 @@ class datetime(date): 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] + 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] if sys.version_info >= (3, 8): @overload # type: ignore[override] - def __sub__(self, __other: timedelta) -> Self: ... + def __sub__(self, __value: timedelta) -> Self: ... @overload - def __sub__(self: _D, __other: _D) -> timedelta: ... + def __sub__(self: _D, __value: _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: ... + def __add__(self, __value: timedelta) -> datetime: ... + def __radd__(self, __value: timedelta) -> datetime: ... @overload # type: ignore[override] - def __sub__(self, __other: datetime) -> timedelta: ... + def __sub__(self, __value: datetime) -> timedelta: ... @overload - def __sub__(self, __other: timedelta) -> datetime: ... + def __sub__(self, __value: timedelta) -> datetime: ... diff --git a/mypy/typeshed/stdlib/difflib.pyi b/mypy/typeshed/stdlib/difflib.pyi index 3105196026958..894ebaaeca983 100644 --- a/mypy/typeshed/stdlib/difflib.pyi +++ b/mypy/typeshed/stdlib/difflib.pyi @@ -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 ac0c5356f5f98..ab101a517a6f3 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,10 +42,10 @@ _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): @@ -54,7 +57,7 @@ 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): diff --git a/mypy/typeshed/stdlib/distutils/__init__.pyi b/mypy/typeshed/stdlib/distutils/__init__.pyi index e69de29bb2d1d..328a5b7834419 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/cmd.pyi b/mypy/typeshed/stdlib/distutils/cmd.pyi index d9ffee9cb8322..61fce37b80bcd 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 80cd78936cb96..5eb541fb91019 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 81fdf76b2b598..7077c9a4c158a 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 56081f9213782..7b0bdd1b35bd1 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 = ..., diff --git a/mypy/typeshed/stdlib/distutils/cygwinccompiler.pyi b/mypy/typeshed/stdlib/distutils/cygwinccompiler.pyi index 1f85b254860b0..a990c3e28f363 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_extensions 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 b411324c4ce67..b296b11f73ba6 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 153583be6b5dd..f9916d4511b2f 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/sysconfig.pyi b/mypy/typeshed/stdlib/distutils/sysconfig.pyi index 8b291e8b94a52..464cfb639c6d7 100644 --- a/mypy/typeshed/stdlib/distutils/sysconfig.pyi +++ b/mypy/typeshed/stdlib/distutils/sysconfig.pyi @@ -1,9 +1,15 @@ +import sys from collections.abc import Mapping from distutils.ccompiler import CCompiler 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: ... def get_config_var(name: str) -> int | str | None: ... def get_config_vars(*args: str) -> Mapping[str, int | str]: ... def get_config_h_filename() -> str: ... @@ -11,3 +17,6 @@ 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 f038443075810..83b03747fda65 100644 --- a/mypy/typeshed/stdlib/distutils/util.pyi +++ b/mypy/typeshed/stdlib/distutils/util.pyi @@ -1,8 +1,12 @@ +import sys from _typeshed import StrPath, Unused from collections.abc import Callable, Container, Iterable, Mapping from typing import Any from typing_extensions import Literal +if sys.version_info >= (3, 8): + 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/email/charset.pyi b/mypy/typeshed/stdlib/email/charset.pyi index 24b8fd768b7b0..e612847c75b68 100644 --- a/mypy/typeshed/stdlib/email/charset.pyi +++ b/mypy/typeshed/stdlib/email/charset.pyi @@ -20,7 +20,7 @@ class Charset: def header_encode_lines(self, string: str, maxlengths: Iterator[int]) -> list[str]: ... def body_encode(self, string: str) -> 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/generator.pyi b/mypy/typeshed/stdlib/email/generator.pyi index 8362dd9c4ff6f..faa6551fc925e 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 c6f0c6fbf6fc1..fc9d73331bae9 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/message.pyi b/mypy/typeshed/stdlib/email/message.pyi index 14e0180731031..18852f4d3bb20 100644 --- a/mypy/typeshed/stdlib/email/message.pyi +++ b/mypy/typeshed/stdlib/email/message.pyi @@ -80,9 +80,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: ... @@ -105,7 +105,7 @@ class Message: class MIMEPart(Message): def __init__(self, policy: Policy | None = None) -> None: ... - def get_body(self, preferencelist: Sequence[str] = ...) -> Message | None: ... + def get_body(self, preferencelist: Sequence[str] = ("related", "html", "plain")) -> Message | None: ... def iter_attachments(self) -> Iterator[Message]: ... def iter_parts(self) -> Iterator[Message]: ... def get_content(self, *args: Any, content_manager: ContentManager | None = None, **kw: Any) -> Any: ... diff --git a/mypy/typeshed/stdlib/email/policy.pyi b/mypy/typeshed/stdlib/email/policy.pyi index 4df3c1e48b07c..dc7f18489bfa0 100644 --- a/mypy/typeshed/stdlib/email/policy.pyi +++ b/mypy/typeshed/stdlib/email/policy.pyi @@ -5,6 +5,7 @@ from email.errors import MessageDefect from email.header import Header from email.message import Message from typing import Any +from typing_extensions import Self __all__ = ["Compat32", "compat32", "Policy", "EmailPolicy", "default", "strict", "SMTP", "HTTP"] @@ -25,7 +26,7 @@ class Policy(metaclass=ABCMeta): mangle_from_: bool = ..., message_factory: Callable[[Policy], Message] | None = ..., ) -> None: ... - def clone(self, **kw: Any) -> Policy: ... + def clone(self, **kw: Any) -> Self: ... 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: ... diff --git a/mypy/typeshed/stdlib/email/utils.pyi b/mypy/typeshed/stdlib/email/utils.pyi index 090ddf9e31bc8..ed63b6b323127 100644 --- a/mypy/typeshed/stdlib/email/utils.pyi +++ b/mypy/typeshed/stdlib/email/utils.pyi @@ -1,5 +1,6 @@ import datetime import sys +from _typeshed import Unused from email import _ParamType from email.charset import Charset from typing import overload @@ -51,7 +52,13 @@ 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): + 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 encode_rfc2231(s: str, charset: str | None = None, language: str | None = None) -> str: ... diff --git a/mypy/typeshed/stdlib/encodings/utf_8_sig.pyi b/mypy/typeshed/stdlib/encodings/utf_8_sig.pyi index 150fe22f8f6eb..af69217d67321 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 b46fe429cacbe..96a96dbce10e7 100644 --- a/mypy/typeshed/stdlib/enum.pyi +++ b/mypy/typeshed/stdlib/enum.pyi @@ -4,7 +4,7 @@ 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 collections.abc import Callable, Iterable, Iterator, Mapping from typing import Any, Generic, TypeVar, overload from typing_extensions import Literal, Self, TypeAlias @@ -34,6 +34,9 @@ if sys.version_info >= (3, 11): "verify", ] +if sys.version_info >= (3, 12): + __all__ += ["pickle_by_enum_name", "pickle_by_global_name"] + _EnumMemberT = TypeVar("_EnumMemberT") _EnumerationT = TypeVar("_EnumerationT", bound=type[Enum]) @@ -106,7 +109,15 @@ 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]: ... @@ -114,7 +125,7 @@ class EnumMeta(ABCMeta): def __bool__(self) -> Literal[True]: ... def __dir__(self) -> list[str]: ... # Simple value lookup - @overload # type: ignore[override] + @overload def __call__(cls: type[_EnumMemberT], value: Any, names: None = None) -> _EnumMemberT: ... # Functional Enum API if sys.version_info >= (3, 11): @@ -200,13 +211,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,33 +231,14 @@ 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 @@ -281,3 +266,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, 12): + 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/fcntl.pyi b/mypy/typeshed/stdlib/fcntl.pyi index 90676e3657123..6aec7515f330b 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_extensions import Buffer, Literal 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,11 +33,9 @@ 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 @@ -104,13 +105,19 @@ if sys.platform != "win32": def fcntl(__fd: FileDescriptorLike, __cmd: int, __arg: int = 0) -> int: ... @overload 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: ... + # 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: ... @overload - def ioctl(__fd: FileDescriptorLike, __request: int, __arg: ReadOnlyBuffer, __mutate_flag: bool = True) -> bytes: ... + 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/fractions.pyi b/mypy/typeshed/stdlib/fractions.pyi index 97cefc916d9ba..7ec8addeb1367 100644 --- a/mypy/typeshed/stdlib/fractions.pyi +++ b/mypy/typeshed/stdlib/fractions.pyi @@ -22,11 +22,9 @@ 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 @@ -34,6 +32,8 @@ class Fraction(Rational): def limit_denominator(self, max_denominator: int = 1000000) -> Fraction: ... if sys.version_info >= (3, 8): def as_integer_ratio(self) -> tuple[int, int]: ... + if sys.version_info >= (3, 12): + def is_integer(self) -> bool: ... @property def numerator(a) -> int: ... @@ -103,14 +103,25 @@ class Fraction(Rational): def __rmod__(b, a: int | Fraction) -> Fraction: ... @overload def __rmod__(b, a: float) -> float: ... - @overload - def __divmod__(a, b: int | Fraction) -> tuple[int, Fraction]: ... - @overload - def __divmod__(a, b: float) -> tuple[float, Fraction]: ... - @overload - def __rdivmod__(b, a: int | Fraction) -> tuple[int, Fraction]: ... - @overload - def __rdivmod__(b, a: float) -> tuple[float, Fraction]: ... + if sys.version_info >= (3, 8): + @overload + def __divmod__(a, b: int | Fraction) -> tuple[int, Fraction]: ... + @overload + def __divmod__(a, b: float) -> tuple[float, Fraction]: ... + @overload + def __rdivmod__(a, b: int | Fraction) -> tuple[int, Fraction]: ... + @overload + def __rdivmod__(a, b: float) -> tuple[float, Fraction]: ... + else: + @overload + def __divmod__(self, other: int | Fraction) -> tuple[int, Fraction]: ... + @overload + def __divmod__(self, other: float) -> tuple[float, Fraction]: ... + @overload + def __rdivmod__(self, other: int | Fraction) -> tuple[int, Fraction]: ... + @overload + def __rdivmod__(self, other: 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 76d9dc02a5da5..f24d14fbf2b68 100644 --- a/mypy/typeshed/stdlib/ftplib.pyi +++ b/mypy/typeshed/stdlib/ftplib.pyi @@ -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 = ..., + source_address: tuple[str, int] | None = None, + encoding: str = "utf-8", + ) -> None: ... + elif sys.version_info >= (3, 9): def __init__( self, host: str = "", diff --git a/mypy/typeshed/stdlib/functools.pyi b/mypy/typeshed/stdlib/functools.pyi index 1214e349f605a..1b4e59b7c1204 100644 --- a/mypy/typeshed/stdlib/functools.pyi +++ b/mypy/typeshed/stdlib/functools.pyi @@ -3,7 +3,7 @@ import types from _typeshed import IdentityFunction, 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_extensions import Literal, Self, TypeAlias, TypedDict, final if sys.version_info >= (3, 9): from types import GenericAlias @@ -41,15 +41,23 @@ 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: ... + if sys.version_info >= (3, 9): + def cache_parameters(self) -> _CacheParameters: ... + def __copy__(self) -> _lru_cache_wrapper[_T]: ... def __deepcopy__(self, __memo: Any) -> _lru_cache_wrapper[_T]: ... @@ -62,13 +70,47 @@ if sys.version_info >= (3, 8): 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__"] -] +if sys.version_info >= (3, 12): + WRAPPER_ASSIGNMENTS: tuple[ + Literal["__module__"], + Literal["__name__"], + Literal["__qualname__"], + Literal["__doc__"], + Literal["__annotations__"], + Literal["__type_params__"], + ] +else: + 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: ... +if sys.version_info >= (3, 12): + def update_wrapper( + wrapper: _T, + wrapped: _AnyCallable, + assigned: Sequence[str] = ("__module__", "__name__", "__qualname__", "__doc__", "__annotations__", "__type_params__"), + updated: Sequence[str] = ("__dict__",), + ) -> _T: ... + def wraps( + wrapped: _AnyCallable, + assigned: Sequence[str] = ("__module__", "__name__", "__qualname__", "__doc__", "__annotations__", "__type_params__"), + updated: Sequence[str] = ("__dict__",), + ) -> IdentityFunction: ... + +else: + def update_wrapper( + wrapper: _T, + wrapped: _AnyCallable, + assigned: Sequence[str] = ("__module__", "__name__", "__qualname__", "__doc__", "__annotations__"), + updated: Sequence[str] = ("__dict__",), + ) -> _T: ... + def wraps( + wrapped: _AnyCallable, + assigned: Sequence[str] = ("__module__", "__name__", "__qualname__", "__doc__", "__annotations__"), + updated: Sequence[str] = ("__dict__",), + ) -> IdentityFunction: ... + def total_ordering(cls: type[_T]) -> type[_T]: ... def cmp_to_key(mycmp: Callable[[_T, _T], int]) -> Callable[[_T], SupportsAllComparisons]: ... @@ -144,10 +186,12 @@ if sys.version_info >= (3, 8): 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]: ... + def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ... @overload def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: ... 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) -> None: ... if sys.version_info >= (3, 9): def __class_getitem__(cls, item: Any) -> GenericAlias: ... diff --git a/mypy/typeshed/stdlib/getopt.pyi b/mypy/typeshed/stdlib/getopt.pyi index 14d63dbd6f99a..bc9d4da4796b2 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 5d98227ec1f44..57e81120b8caa 100644 --- a/mypy/typeshed/stdlib/gettext.pyi +++ b/mypy/typeshed/stdlib/gettext.pyi @@ -57,8 +57,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 diff --git a/mypy/typeshed/stdlib/gzip.pyi b/mypy/typeshed/stdlib/gzip.pyi index 6a794f381ad6f..1ec8b4b8ca7c2 100644 --- a/mypy/typeshed/stdlib/gzip.pyi +++ b/mypy/typeshed/stdlib/gzip.pyi @@ -1,7 +1,7 @@ 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 @@ -159,9 +159,9 @@ 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: ... + def compress(data: SizedBuffer, compresslevel: int = 9, *, mtime: float | None = None) -> bytes: ... else: - def compress(data: _BufferWithLen, compresslevel: int = 9) -> bytes: ... + def compress(data: SizedBuffer, compresslevel: int = 9) -> bytes: ... def decompress(data: ReadableBuffer) -> bytes: ... diff --git a/mypy/typeshed/stdlib/heapq.pyi b/mypy/typeshed/stdlib/heapq.pyi index 61418b3704d6f..3f4f274b97691 100644 --- a/mypy/typeshed/stdlib/heapq.pyi +++ b/mypy/typeshed/stdlib/heapq.pyi @@ -15,4 +15,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 b9a867f7bd619..9ff99a5a05d57 100644 --- a/mypy/typeshed/stdlib/hmac.pyi +++ b/mypy/typeshed/stdlib/hmac.pyi @@ -1,5 +1,5 @@ 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 @@ -30,7 +30,13 @@ class HMAC: block_size: int @property def name(self) -> str: ... - def __init__(self, key: bytes | bytearray, msg: ReadableBuffer | None = None, digestmod: _DigestMod = "") -> None: ... + if sys.version_info >= (3, 8): + def __init__(self, key: bytes | bytearray, msg: ReadableBuffer | None = None, digestmod: _DigestMod = "") -> None: ... + else: + def __init__( + self, key: bytes | bytearray, msg: ReadableBuffer | None = None, digestmod: _DigestMod | None = None + ) -> None: ... + def update(self, msg: ReadableBuffer) -> None: ... def digest(self) -> bytes: ... def hexdigest(self) -> str: ... @@ -40,4 +46,4 @@ class HMAC: 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 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 d4b44f2eb99b1..4310c79b92690 100644 --- a/mypy/typeshed/stdlib/http/__init__.pyi +++ b/mypy/typeshed/stdlib/http/__init__.pyi @@ -79,6 +79,17 @@ class HTTPStatus(IntEnum): 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): diff --git a/mypy/typeshed/stdlib/http/client.pyi b/mypy/typeshed/stdlib/http/client.pyi index b1506b50e7504..41ece1b050b8d 100644 --- a/mypy/typeshed/stdlib/http/client.pyi +++ b/mypy/typeshed/stdlib/http/client.pyi @@ -1,6 +1,7 @@ import email.message import io import ssl +import sys import types from _typeshed import ReadableBuffer, SupportsRead, WriteableBuffer from collections.abc import Callable, Iterable, Iterator, Mapping @@ -101,7 +102,7 @@ class HTTPMessage(email.message.Message): def parse_headers(fp: io.BufferedIOBase, _class: Callable[[], email.message.Message] = ...) -> 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 @@ -158,7 +159,7 @@ class HTTPConnection: method: str, url: str, body: _DataType | str | None = None, - headers: Mapping[str, str] = ..., + headers: Mapping[str, str] = {}, *, encode_chunked: bool = False, ) -> None: ... @@ -174,20 +175,32 @@ class HTTPConnection: 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: str | 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 7f2c9c6cc8f43..482cbca1d88aa 100644 --- a/mypy/typeshed/stdlib/http/cookiejar.pyi +++ b/mypy/typeshed/stdlib/http/cookiejar.pyi @@ -99,7 +99,7 @@ class DefaultCookiePolicy(CookiePolicy): strict_ns_domain: int = 0, strict_ns_set_initial_dollar: bool = False, strict_ns_set_path: bool = False, - secure_protocols: Sequence[str] = ..., + secure_protocols: Sequence[str] = ("https", "wss"), ) -> None: ... else: def __init__( diff --git a/mypy/typeshed/stdlib/imaplib.pyi b/mypy/typeshed/stdlib/imaplib.pyi index 1c2112dd37c8f..7781559c38885 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 @@ -155,7 +155,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/importlib/__init__.pyi b/mypy/typeshed/stdlib/importlib/__init__.pyi index 1747b274136ed..8d73319f8c3dd 100644 --- a/mypy/typeshed/stdlib/importlib/__init__.pyi +++ b/mypy/typeshed/stdlib/importlib/__init__.pyi @@ -9,7 +9,7 @@ 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: ... diff --git a/mypy/typeshed/stdlib/importlib/abc.pyi b/mypy/typeshed/stdlib/importlib/abc.pyi index 3d0c2d38c4e9f..4bf46104ba6df 100644 --- a/mypy/typeshed/stdlib/importlib/abc.pyi +++ b/mypy/typeshed/stdlib/importlib/abc.pyi @@ -1,3 +1,4 @@ +import _ast import sys import types from _typeshed import ( @@ -7,6 +8,7 @@ from _typeshed import ( OpenBinaryModeWriting, OpenTextMode, ReadableBuffer, + StrPath, ) from abc import ABCMeta, abstractmethod from collections.abc import Iterator, Mapping, Sequence @@ -52,7 +54,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 @@ -191,7 +195,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 open_resource(self, resource: str) -> BufferedReader: ... def resource_path(self, resource: Any) -> NoReturn: ... def is_resource(self, path: str) -> bool: ... def contents(self) -> Iterator[str]: ... diff --git a/mypy/typeshed/stdlib/importlib/resources.pyi b/mypy/typeshed/stdlib/importlib/resources/__init__.pyi similarity index 100% rename from mypy/typeshed/stdlib/importlib/resources.pyi rename to mypy/typeshed/stdlib/importlib/resources/__init__.pyi diff --git a/mypy/typeshed/stdlib/importlib/resources/abc.pyi b/mypy/typeshed/stdlib/importlib/resources/abc.pyi new file mode 100644 index 0000000000000..a36c952d01acf --- /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/inspect.pyi b/mypy/typeshed/stdlib/inspect.pyi index 2525ef4968eca..9af4c39bae9e2 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 ( @@ -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,20 +175,31 @@ 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): + @overload + def getmembers_static(object: object, predicate: _GetMembersPredicateTypeGuard[_T]) -> _GetMembersReturnTypeGuard[_T]: ... + @overload def getmembers_static(object: object, predicate: _GetMembersPredicate | None = None) -> _GetMembersReturn: ... -def getmodulename(path: str) -> str | None: ... +def getmodulename(path: StrPath) -> 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, 12): + def markcoroutinefunction(func: _F) -> _F: ... + if sys.version_info >= (3, 8): @overload def isgeneratorfunction(obj: Callable[..., Generator[Any, Any, Any]]) -> bool: ... @@ -351,6 +376,17 @@ class _ParameterKind(enum.IntEnum): @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"] + + 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: ... empty = _empty @@ -442,9 +478,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] = ..., @@ -590,3 +626,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: int + WRITABLE: int + FORMAT: int + ND: int + STRIDES: int + C_CONTIGUOUS: int + F_CONTIGUOUS: int + ANY_CONTIGUOUS: int + INDIRECT: int + CONTIG: int + CONTIG_RO: int + STRIDED: int + STRIDED_RO: int + RECORDS: int + RECORDS_RO: int + FULL: int + FULL_RO: int + READ: int + WRITE: int diff --git a/mypy/typeshed/stdlib/io.pyi b/mypy/typeshed/stdlib/io.pyi index c3e07bacbe5a9..c114f839594fe 100644 --- a/mypy/typeshed/stdlib/io.pyi +++ b/mypy/typeshed/stdlib/io.pyi @@ -90,7 +90,7 @@ class BufferedIOBase(IOBase): 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] def __init__( @@ -102,7 +102,7 @@ class FileIO(RawIOBase, BinaryIO): 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 @@ -113,17 +113,17 @@ class BytesIO(BufferedIOBase, BinaryIO): def getbuffer(self) -> memoryview: ... 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: ... -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: ... -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 @@ -144,7 +144,7 @@ class TextIOBase(IOBase): def readlines(self, __hint: int = -1) -> list[str]: ... # type: ignore[override] def read(self, __size: int | None = ...) -> str: ... -class TextIOWrapper(TextIOBase, TextIO): +class TextIOWrapper(TextIOBase, TextIO): # type: ignore[misc] # incompatible definitions of write in the base classes def __init__( self, buffer: IO[bytes], diff --git a/mypy/typeshed/stdlib/ipaddress.pyi b/mypy/typeshed/stdlib/ipaddress.pyi index 9f96621377650..7a4146885b297 100644 --- a/mypy/typeshed/stdlib/ipaddress.pyi +++ b/mypy/typeshed/stdlib/ipaddress.pyi @@ -36,7 +36,9 @@ class _BaseAddress(_IPAddressBase, SupportsInt): def __add__(self, other: int) -> Self: ... 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): diff --git a/mypy/typeshed/stdlib/itertools.pyi b/mypy/typeshed/stdlib/itertools.pyi index c7b92c3aebb55..4b5d624c78d7c 100644 --- a/mypy/typeshed/stdlib/itertools.pyi +++ b/mypy/typeshed/stdlib/itertools.pyi @@ -272,7 +272,7 @@ if sys.version_info >= (3, 10): 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/__init__.pyi b/mypy/typeshed/stdlib/json/__init__.pyi index 63e9718ee1512..dc0cdff926d4f 100644 --- a/mypy/typeshed/stdlib/json/__init__.pyi +++ b/mypy/typeshed/stdlib/json/__init__.pyi @@ -1,3 +1,4 @@ +import sys from _typeshed import SupportsRead, SupportsWrite from collections.abc import Callable from typing import Any @@ -6,6 +7,8 @@ from .decoder import JSONDecodeError as JSONDecodeError, JSONDecoder as JSONDeco from .encoder import JSONEncoder as JSONEncoder __all__ = ["dump", "dumps", "load", "loads", "JSONDecoder", "JSONDecodeError", "JSONEncoder"] +if sys.version_info >= (3, 12): + __all__ += ["AttrDict"] def dumps( obj: Any, @@ -59,3 +62,9 @@ def load( **kwds: Any, ) -> Any: ... def detect_encoding(b: bytes | bytearray) -> str: ... # undocumented + +if sys.version_info >= (3, 12): + class AttrDict(dict[str, Any]): + def __getattr__(self, name: str) -> Any: ... + def __setattr__(self, name: str, value: Any) -> None: ... + def __delattr__(self, name: str) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/btm_matcher.pyi b/mypy/typeshed/stdlib/lib2to3/btm_matcher.pyi new file mode 100644 index 0000000000000..4c87b664eb200 --- /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 0000000000000..eef386f709aca --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixer_base.pyi @@ -0,0 +1,43 @@ +from _typeshed import Incomplete, StrPath +from abc import ABCMeta, abstractmethod +from collections.abc import MutableMapping +from typing import ClassVar, TypeVar +from typing_extensions import Literal + +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/mypy/typeshed/stdlib/lib2to3/fixes/__init__.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/__init__.pyi new file mode 100644 index 0000000000000..e69de29bb2d1d 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 0000000000000..7c5451c15220c --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_apply.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..bf73009e9dbf0 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_asserts.pyi @@ -0,0 +1,11 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..84a354d327772 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_basestring.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..857c1e2241b98 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_buffer.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..2e66911195bf0 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_dict.pyi @@ -0,0 +1,17 @@ +from _typeshed import Incomplete +from typing import ClassVar +from typing_extensions import 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 0000000000000..b87aacd342e93 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_except.pyi @@ -0,0 +1,15 @@ +from collections.abc import Generator, Iterable +from typing import ClassVar, TypeVar +from typing_extensions import Literal + +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 0000000000000..306937eb9759b --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_exec.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..fb245e5a1c1ca --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_execfile.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..10341d7985a8a --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_exitfunc.pyi @@ -0,0 +1,14 @@ +from _typeshed import Incomplete, StrPath +from lib2to3 import fixer_base +from typing import ClassVar +from typing_extensions import 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 0000000000000..3998a1dd001ea --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_filter.pyi @@ -0,0 +1,10 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..59919446ffdd2 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_funcattrs.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..8eb5ca35dcc3e --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_future.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..d18a38f69be01 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_getcwdu.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..1e6b58dd3512e --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_has_key.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..8f02252f7bb96 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_idioms.pyi @@ -0,0 +1,16 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..436e7f1915b22 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_import.pyi @@ -0,0 +1,17 @@ +from _typeshed import StrPath +from collections.abc import Generator +from typing import ClassVar +from typing_extensions import 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 0000000000000..277a172d3af93 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_imports.pyi @@ -0,0 +1,22 @@ +from _typeshed import StrPath +from collections.abc import Generator +from typing import ClassVar +from typing_extensions import 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 0000000000000..8d55433085dd4 --- /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 0000000000000..df52f8d774276 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_input.pyi @@ -0,0 +1,12 @@ +from _typeshed import Incomplete +from typing import ClassVar +from typing_extensions import 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 0000000000000..f4e71b6da5f20 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_intern.pyi @@ -0,0 +1,10 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..e776ea0437146 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_isinstance.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..a19f7b5e8a001 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_itertools.pyi @@ -0,0 +1,10 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..1ea0b506aaa2c --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_itertools_imports.pyi @@ -0,0 +1,8 @@ +from lib2to3 import fixer_base +from typing import ClassVar +from typing_extensions import 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 0000000000000..c47f4528de479 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_long.pyi @@ -0,0 +1,8 @@ +from lib2to3 import fixer_base +from typing import ClassVar +from typing_extensions import 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 0000000000000..66e311cba8a89 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_map.pyi @@ -0,0 +1,10 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..44626b47072d3 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_metaclass.pyi @@ -0,0 +1,18 @@ +from collections.abc import Generator +from typing import ClassVar +from typing_extensions import 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 0000000000000..9bda7992dc8b3 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_methodattrs.pyi @@ -0,0 +1,11 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..95dfacccf2198 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_ne.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..a5757d65064ad --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_next.pyi @@ -0,0 +1,20 @@ +from _typeshed import StrPath +from typing import ClassVar +from typing_extensions import 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 0000000000000..adf268fdb8e23 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_nonzero.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..6842e42e45f04 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_numliterals.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..6da150a51c0c5 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_operator.pyi @@ -0,0 +1,13 @@ +from lib2to3 import fixer_base +from typing import ClassVar +from typing_extensions import 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 0000000000000..c730cdc5d0b2a --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_paren.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..2261c9489299d --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_print.pyi @@ -0,0 +1,13 @@ +from _typeshed import Incomplete +from typing import ClassVar +from typing_extensions import 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 0000000000000..756a05ea3dddb --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_raise.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..61d6ad7676efd --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_raw_input.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..4ea07fdde00bc --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_reduce.pyi @@ -0,0 +1,9 @@ +from lib2to3 import fixer_base +from typing import ClassVar +from typing_extensions import 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 0000000000000..8045ac5078907 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_reload.pyi @@ -0,0 +1,10 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..2ceca053e903a --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_renames.pyi @@ -0,0 +1,18 @@ +from collections.abc import Generator +from typing import ClassVar +from typing_extensions import 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 0000000000000..6f3305846d18d --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_repr.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..dd18413d6d5a9 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_set_literal.pyi @@ -0,0 +1,8 @@ +from lib2to3 import fixer_base +from typing import ClassVar +from typing_extensions import 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 0000000000000..fd23af5a711e9 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_standarderror.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..3dbcd38c4b261 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_sys_exc.pyi @@ -0,0 +1,10 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..50e37d44a58b8 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_throw.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..48eadf75341c2 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_tuple_params.pyi @@ -0,0 +1,18 @@ +from _typeshed import Incomplete +from typing import ClassVar +from typing_extensions import 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 0000000000000..6ac1344b1e6c9 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_types.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..af63d1865f2d0 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_unicode.pyi @@ -0,0 +1,13 @@ +from _typeshed import StrPath +from typing import ClassVar +from typing_extensions import 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 0000000000000..a37e63b31101b --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_urllib.pyi @@ -0,0 +1,15 @@ +from collections.abc import Generator +from typing_extensions 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 0000000000000..6231d90c65f15 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_ws_comma.pyi @@ -0,0 +1,13 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..89d300ef063ab --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_xrange.pyi @@ -0,0 +1,21 @@ +from _typeshed import Incomplete, StrPath +from typing import ClassVar +from typing_extensions import 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 0000000000000..39757155e5d9b --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_xreadlines.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..0c70717aa2ac6 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_zip.pyi @@ -0,0 +1,10 @@ +from typing import ClassVar +from typing_extensions import 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 0000000000000..cfcaeeaf64ee1 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/main.pyi @@ -0,0 +1,43 @@ +from _typeshed import FileDescriptorOrPath +from collections.abc import Container, Iterable, Iterator, Mapping, Sequence +from logging import _ExcInfoType +from typing import AnyStr +from typing_extensions import 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 acc1cc429be93..de8a874f434d0 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 9f6e4d6774adf..dea13fb9d0f8b 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 51eb671f42363..320c5f018d43f 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 d346739d4d583..6d9f776c61aec 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 2a9c3fbba8212..af54de1b51d33 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 00fdbd1a124eb..2d1e90e799276 100644 --- a/mypy/typeshed/stdlib/lib2to3/pygram.pyi +++ b/mypy/typeshed/stdlib/lib2to3/pygram.pyi @@ -1,5 +1,6 @@ import sys -from lib2to3.pgen2.grammar import Grammar + +from .pgen2.grammar import Grammar class Symbols: def __init__(self, grammar: Grammar) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/pytree.pyi b/mypy/typeshed/stdlib/lib2to3/pytree.pyi index 4f756c9768dbb..d14446f385654 100644 --- a/mypy/typeshed/stdlib/lib2to3/pytree.pyi +++ b/mypy/typeshed/stdlib/lib2to3/pytree.pyi @@ -1,16 +1,19 @@ -from collections.abc import Iterator -from lib2to3.pgen2.grammar import Grammar -from typing import Any -from typing_extensions import Self, TypeAlias +from _typeshed import Incomplete, SupportsGetItem, SupportsLenAndGetItem, Unused +from abc import abstractmethod +from collections.abc import Iterable, Iterator, MutableSequence +from typing_extensions import Final, 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 +23,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 +44,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 +70,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 +93,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 +111,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 f1d89679aee77..d750d9c4a6cf9 100644 --- a/mypy/typeshed/stdlib/lib2to3/refactor.pyi +++ b/mypy/typeshed/stdlib/lib2to3/refactor.pyi @@ -1,12 +1,16 @@ +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, NoReturn, overload +from typing_extensions import Final +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 +25,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/locale.pyi b/mypy/typeshed/stdlib/locale.pyi index 0b0dd9456e52d..3753700ea8890 100644 --- a/mypy/typeshed/stdlib/locale.pyi +++ b/mypy/typeshed/stdlib/locale.pyi @@ -15,7 +15,6 @@ __all__ = [ "str", "atof", "atoi", - "format", "format_string", "currency", "normalize", @@ -32,6 +31,9 @@ __all__ = [ if sys.version_info >= (3, 11): __all__ += ["getencoding"] +if sys.version_info < (3, 12): + __all__ += ["format"] + # 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 @@ -114,14 +116,21 @@ 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 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: ... diff --git a/mypy/typeshed/stdlib/logging/__init__.pyi b/mypy/typeshed/stdlib/logging/__init__.pyi index c74afa45ded1d..6ebd305aacb89 100644 --- a/mypy/typeshed/stdlib/logging/__init__.pyi +++ b/mypy/typeshed/stdlib/logging/__init__.pyi @@ -80,7 +80,7 @@ _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: ... @@ -116,74 +116,74 @@ class Logger(Filterer): 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, ) -> 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, ) -> 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, ) -> 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, ) -> None: ... def error( 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, ) -> None: ... def exception( self, msg: object, *args: object, exc_info: _ExcInfoType = True, - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, ) -> None: ... def critical( 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, ) -> None: ... def log( self, level: int, 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, ) -> None: ... def _log( self, @@ -200,66 +200,66 @@ class Logger(Filterer): self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, ) -> None: ... def info( self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, ) -> None: ... def warning( self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, ) -> None: ... def warn( self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, ) -> None: ... def error( self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, ) -> None: ... def critical( self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, ) -> 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, + extra: Mapping[str, object] | None = None, ) -> None: ... def exception( self, msg: object, *args: object, exc_info: _ExcInfoType = True, - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + stack_info: bool = False, + extra: Mapping[str, object] | None = None, ) -> None: ... def _log( self, @@ -389,7 +389,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 @@ -432,50 +432,50 @@ class LoggerAdapter(Generic[_L]): 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 = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, **kwargs: object, ) -> None: ... def exception( @@ -483,19 +483,19 @@ class LoggerAdapter(Generic[_L]): msg: object, *args: object, exc_info: _ExcInfoType = True, - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, **kwargs: object, ) -> None: ... def critical( 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 log( @@ -503,10 +503,10 @@ class LoggerAdapter(Generic[_L]): level: int, 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: ... else: @@ -514,45 +514,45 @@ class LoggerAdapter(Generic[_L]): self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, **kwargs: object, ) -> None: ... def info( self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, **kwargs: object, ) -> None: ... def warning( self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, **kwargs: object, ) -> None: ... def warn( self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, **kwargs: object, ) -> None: ... def error( self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, **kwargs: object, ) -> None: ... def exception( @@ -560,17 +560,17 @@ class LoggerAdapter(Generic[_L]): msg: object, *args: object, exc_info: _ExcInfoType = True, - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + stack_info: bool = False, + extra: Mapping[str, object] | None = None, **kwargs: object, ) -> None: ... def critical( self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, **kwargs: object, ) -> None: ... def log( @@ -578,9 +578,9 @@ class LoggerAdapter(Generic[_L]): 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, + extra: Mapping[str, object] | None = None, **kwargs: object, ) -> None: ... @@ -610,102 +610,126 @@ if sys.version_info >= (3, 8): def debug( 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, ) -> None: ... def info( 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, ) -> None: ... def warning( 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, ) -> None: ... def warn( 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, ) -> None: ... def error( 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, ) -> 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, ) -> None: ... def exception( msg: object, *args: object, exc_info: _ExcInfoType = True, - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., + 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 = ..., - 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, ) -> None: ... else: def debug( - msg: object, *args: object, exc_info: _ExcInfoType = ..., stack_info: bool = ..., extra: Mapping[str, object] | None = ... + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, ) -> None: ... def info( - msg: object, *args: object, exc_info: _ExcInfoType = ..., stack_info: bool = ..., extra: Mapping[str, object] | None = ... + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, ) -> None: ... def warning( - msg: object, *args: object, exc_info: _ExcInfoType = ..., stack_info: bool = ..., extra: Mapping[str, object] | None = ... + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, ) -> None: ... def warn( - msg: object, *args: object, exc_info: _ExcInfoType = ..., stack_info: bool = ..., extra: Mapping[str, object] | None = ... + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, ) -> None: ... def error( - msg: object, *args: object, exc_info: _ExcInfoType = ..., stack_info: bool = ..., extra: Mapping[str, object] | None = ... + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, ) -> None: ... def critical( - msg: object, *args: object, exc_info: _ExcInfoType = ..., stack_info: bool = ..., extra: Mapping[str, object] | None = ... + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + extra: Mapping[str, object] | None = None, ) -> None: ... def exception( msg: object, *args: object, exc_info: _ExcInfoType = True, - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + stack_info: bool = False, + extra: Mapping[str, object] | None = None, ) -> None: ... def log( 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, + extra: Mapping[str, object] | None = None, ) -> None: ... fatal = critical diff --git a/mypy/typeshed/stdlib/logging/config.pyi b/mypy/typeshed/stdlib/logging/config.pyi index f76f655a61968..e92658f7f1b34 100644 --- a/mypy/typeshed/stdlib/logging/config.pyi +++ b/mypy/typeshed/stdlib/logging/config.pyi @@ -1,36 +1,59 @@ 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, overload +from typing_extensions import Literal, SupportsIndex, TypeAlias, TypedDict -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 +if sys.version_info >= (3, 8): + _FormatterConfigurationTypedDict = TypedDict( + "_FormatterConfigurationTypedDict", {"class": str, "format": str, "datefmt": str, "style": _FormatStyle}, total=False + ) +else: + _FormatterConfigurationTypedDict = TypedDict( + "_FormatterConfigurationTypedDict", + {"class": str, "format": str, "datefmt": str, "style": _FormatStyle, "validate": bool}, + 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 _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]] + formatters: dict[str, _FormatterConfiguration] + filters: dict[str, _FilterConfiguration] + handlers: dict[str, _HandlerConfiguration] loggers: dict[str, _LoggerConfiguration] root: _RootLoggerConfiguration | None incremental: bool @@ -64,3 +87,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 7e0bfd705895b..ad5bf392b50fc 100644 --- a/mypy/typeshed/stdlib/logging/handlers.pyi +++ b/mypy/typeshed/stdlib/logging/handlers.pyi @@ -5,10 +5,11 @@ 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 typing import Any, ClassVar, Protocol, TypeVar + +_T = TypeVar("_T") DEFAULT_TCP_LOGGING_PORT: int DEFAULT_UDP_LOGGING_PORT: int @@ -177,7 +178,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 +250,21 @@ 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 + 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 34bd6f3f8db1a..8e296bb5b357f 100644 --- a/mypy/typeshed/stdlib/lzma.pyi +++ b/mypy/typeshed/stdlib/lzma.pyi @@ -104,7 +104,7 @@ class LZMACompressor: class LZMAError(Exception): ... -class LZMAFile(io.BufferedIOBase, IO[bytes]): +class LZMAFile(io.BufferedIOBase, IO[bytes]): # type: ignore[misc] # incompatible definitions of writelines in the base classes def __init__( self, filename: _PathOrFile | None = None, diff --git a/mypy/typeshed/stdlib/mailcap.pyi b/mypy/typeshed/stdlib/mailcap.pyi index 5905f5826bf7c..ce549e01f528c 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/math.pyi b/mypy/typeshed/stdlib/math.pyi index 231964f397db4..4a4d592b860d1 100644 --- a/mypy/typeshed/stdlib/math.pyi +++ b/mypy/typeshed/stdlib/math.pyi @@ -112,7 +112,10 @@ def log1p(__x: _SupportsFloatOrIndex) -> float: ... def log2(__x: _SupportsFloatOrIndex) -> float: ... def modf(__x: _SupportsFloatOrIndex) -> tuple[float, float]: ... -if sys.version_info >= (3, 9): +if sys.version_info >= (3, 12): + def nextafter(__x: _SupportsFloatOrIndex, __y: _SupportsFloatOrIndex, *, steps: SupportsIndex | None = None) -> float: ... + +elif sys.version_info >= (3, 9): def nextafter(__x: _SupportsFloatOrIndex, __y: _SupportsFloatOrIndex) -> float: ... if sys.version_info >= (3, 8): @@ -130,6 +133,10 @@ 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, 12): + def sumprod(__p: Iterable[float], __q: Iterable[float]) -> float: ... + def sqrt(__x: _SupportsFloatOrIndex) -> float: ... def tan(__x: _SupportsFloatOrIndex) -> float: ... def tanh(__x: _SupportsFloatOrIndex) -> float: ... diff --git a/mypy/typeshed/stdlib/mimetypes.pyi b/mypy/typeshed/stdlib/mimetypes.pyi index fd3908680009e..128a05fa57525 100644 --- a/mypy/typeshed/stdlib/mimetypes.pyi +++ b/mypy/typeshed/stdlib/mimetypes.pyi @@ -43,7 +43,7 @@ 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]: ... diff --git a/mypy/typeshed/stdlib/mmap.pyi b/mypy/typeshed/stdlib/mmap.pyi index c74ad3cda6db6..38e1924392c49 100644 --- a/mypy/typeshed/stdlib/mmap.pyi +++ b/mypy/typeshed/stdlib/mmap.pyi @@ -60,14 +60,14 @@ 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: ... @@ -76,6 +76,8 @@ class mmap(Iterable[int], Sized): 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": MADV_NORMAL: int diff --git a/mypy/typeshed/stdlib/modulefinder.pyi b/mypy/typeshed/stdlib/modulefinder.pyi index 6f1917644b066..06bb50d262869 100644 --- a/mypy/typeshed/stdlib/modulefinder.pyi +++ b/mypy/typeshed/stdlib/modulefinder.pyi @@ -44,8 +44,8 @@ class ModuleFinder: self, path: list[str] | None = None, debug: int = 0, - excludes: Container[str] = ..., - replace_paths: Sequence[tuple[str, str]] = ..., + excludes: Container[str] = [], + replace_paths: Sequence[tuple[str, str]] = [], ) -> None: ... def msg(self, level: int, str: str, *args: Any) -> None: ... # undocumented diff --git a/mypy/typeshed/stdlib/multiprocessing/connection.pyi b/mypy/typeshed/stdlib/multiprocessing/connection.pyi index d034373712e05..28696fe6a3a38 100644 --- a/mypy/typeshed/stdlib/multiprocessing/connection.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/connection.pyi @@ -52,7 +52,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 c498649a7b61f..fe3b980245485 100644 --- a/mypy/typeshed/stdlib/multiprocessing/context.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/context.pyi @@ -72,7 +72,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 diff --git a/mypy/typeshed/stdlib/multiprocessing/dummy/__init__.pyi b/mypy/typeshed/stdlib/multiprocessing/dummy/__init__.pyi index 5b2a33772de65..967b57ded6c87 100644 --- a/mypy/typeshed/stdlib/multiprocessing/dummy/__init__.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/dummy/__init__.pyi @@ -50,8 +50,8 @@ 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 @@ -69,9 +69,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 fcd03a6573195..d7e982129466d 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 df435f00ebe77..9a15f2683b7d4 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 e035a18756507..27a903fb9987b 100644 --- a/mypy/typeshed/stdlib/multiprocessing/managers.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/managers.pyi @@ -54,7 +54,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]]]: ... @@ -116,8 +116,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 +150,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 @@ -197,6 +197,8 @@ class SyncManager(BaseManager): @overload def dict(self, __iterable: Iterable[list[str]]) -> DictProxy[str, str]: ... @overload + 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]: ... diff --git a/mypy/typeshed/stdlib/multiprocessing/pool.pyi b/mypy/typeshed/stdlib/multiprocessing/pool.pyi index a19dd555e2542..dd4b865a3574d 100644 --- a/mypy/typeshed/stdlib/multiprocessing/pool.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/pool.pyi @@ -72,16 +72,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]: ... @@ -115,7 +115,7 @@ class Pool: 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 diff --git a/mypy/typeshed/stdlib/multiprocessing/process.pyi b/mypy/typeshed/stdlib/multiprocessing/process.pyi index ef1b4b596d339..9863013fc05fd 100644 --- a/mypy/typeshed/stdlib/multiprocessing/process.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/process.pyi @@ -17,8 +17,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: ... diff --git a/mypy/typeshed/stdlib/multiprocessing/queues.pyi b/mypy/typeshed/stdlib/multiprocessing/queues.pyi index 7ba17dcfbe058..8e72d15f25f63 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/resource_tracker.pyi b/mypy/typeshed/stdlib/multiprocessing/resource_tracker.pyi index e2b940796126f..7f726a00d73ae 100644 --- a/mypy/typeshed/stdlib/multiprocessing/resource_tracker.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/resource_tracker.pyi @@ -9,7 +9,7 @@ class ResourceTracker: def register(self, name: Sized, rtype: Incomplete) -> None: ... def unregister(self, name: Sized, rtype: Incomplete) -> 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/synchronize.pyi b/mypy/typeshed/stdlib/multiprocessing/synchronize.pyi index 7043759078a2e..a4e36cfa0b6e6 100644 --- a/mypy/typeshed/stdlib/multiprocessing/synchronize.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/synchronize.pyi @@ -14,9 +14,6 @@ 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]): def __init__(self, lock: _LockLike | None = None, *, ctx: BaseContext) -> None: ... def notify(self, n: int = 1) -> None: ... @@ -30,12 +27,20 @@ class Condition(AbstractContextManager[bool]): ) -> 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]): + 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 006ec3a9f6ceb..7ca650511e511 100644 --- a/mypy/typeshed/stdlib/multiprocessing/util.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/util.pyi @@ -50,14 +50,14 @@ class Finalize: self, obj: Incomplete | None, callback: Callable[..., Incomplete], - args: Sequence[Any] = ..., + 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: ... diff --git a/mypy/typeshed/stdlib/opcode.pyi b/mypy/typeshed/stdlib/opcode.pyi index 1232454e71ea6..f852489ffacfd 100644 --- a/mypy/typeshed/stdlib/opcode.pyi +++ b/mypy/typeshed/stdlib/opcode.pyi @@ -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,6 +45,11 @@ 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] @@ -53,5 +61,3 @@ if sys.version_info >= (3, 8): else: def stack_effect(__opcode: int, __oparg: int | None = None) -> int: ... - -hasnargs: list[int] diff --git a/mypy/typeshed/stdlib/os/__init__.pyi b/mypy/typeshed/stdlib/os/__init__.pyi index 595b78789c6a6..efe80d82ffbab 100644 --- a/mypy/typeshed/stdlib/os/__init__.pyi +++ b/mypy/typeshed/stdlib/os/__init__.pyi @@ -885,7 +885,10 @@ def times() -> times_result: ... 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, 8): + def startfile(path: StrOrBytesPath, operation: str | None = None) -> None: ... + else: + def startfile(filepath: StrOrBytesPath, operation: str | None = None) -> None: ... else: def spawnlp(mode: int, file: StrOrBytesPath, arg0: StrOrBytesPath, *args: StrOrBytesPath) -> int: ... diff --git a/mypy/typeshed/stdlib/pathlib.pyi b/mypy/typeshed/stdlib/pathlib.pyi index 114678ed574d2..3c2ae0fe7ab1a 100644 --- a/mypy/typeshed/stdlib/pathlib.pyi +++ b/mypy/typeshed/stdlib/pathlib.pyi @@ -55,7 +55,11 @@ class PurePath(PathLike[str]): if sys.version_info >= (3, 9): def is_relative_to(self, *other: StrPath) -> bool: ... - def match(self, path_pattern: str) -> bool: ... + 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: ... + def relative_to(self, *other: StrPath) -> Self: ... def with_name(self, name: str) -> Self: ... if sys.version_info >= (3, 9): @@ -70,6 +74,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 +93,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 +109,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,6 +176,10 @@ 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): @@ -171,11 +192,10 @@ class Path(PurePath): def replace(self, target: str | PurePath) -> None: ... 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): diff --git a/mypy/typeshed/stdlib/pdb.pyi b/mypy/typeshed/stdlib/pdb.pyi index e2871bb54fa0c..e0d69e7d30fa6 100644 --- a/mypy/typeshed/stdlib/pdb.pyi +++ b/mypy/typeshed/stdlib/pdb.pyi @@ -128,7 +128,7 @@ class Pdb(Bdb, Cmd): 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 +168,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 57c4cb03e484b..cf3995d74f5d0 100644 --- a/mypy/typeshed/stdlib/pickle.pyi +++ b/mypy/typeshed/stdlib/pickle.pyi @@ -103,6 +103,8 @@ if sys.version_info >= (3, 8): 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 def dump( obj: Any, @@ -121,7 +123,7 @@ if sys.version_info >= (3, 8): fix_imports: bool = True, encoding: str = "ASCII", errors: str = "strict", - buffers: Iterable[Any] | None = ..., + buffers: Iterable[Any] | None = (), ) -> Any: ... def loads( __data: ReadableBuffer, @@ -129,7 +131,7 @@ if sys.version_info >= (3, 8): fix_imports: bool = True, encoding: str = "ASCII", errors: str = "strict", - buffers: Iterable[Any] | None = ..., + buffers: Iterable[Any] | None = (), ) -> Any: ... else: diff --git a/mypy/typeshed/stdlib/pkgutil.pyi b/mypy/typeshed/stdlib/pkgutil.pyi index f9808c9e5de82..59f1f734cf90b 100644 --- a/mypy/typeshed/stdlib/pkgutil.pyi +++ b/mypy/typeshed/stdlib/pkgutil.pyi @@ -12,12 +12,12 @@ __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]) @@ -28,11 +28,12 @@ class ModuleInfo(NamedTuple): 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: ... diff --git a/mypy/typeshed/stdlib/platform.pyi b/mypy/typeshed/stdlib/platform.pyi index 291f302b4c7dc..483d6c454c2ea 100644 --- a/mypy/typeshed/stdlib/platform.pyi +++ b/mypy/typeshed/stdlib/platform.pyi @@ -33,10 +33,10 @@ if sys.version_info >= (3, 8): 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/pydoc.pyi b/mypy/typeshed/stdlib/pydoc.pyi index c6893d50c66ae..c993af390bbb2 100644 --- a/mypy/typeshed/stdlib/pydoc.pyi +++ b/mypy/typeshed/stdlib/pydoc.pyi @@ -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 @@ -70,7 +70,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 +113,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 +126,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 +136,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] @@ -154,7 +154,7 @@ class TextRepr(Repr): 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 +195,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, 12): + 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 +228,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, 12): + 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/random.pyi b/mypy/typeshed/stdlib/random.pyi index 4849878691f5c..9fd1c64f2bba2 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/reprlib.pyi b/mypy/typeshed/stdlib/reprlib.pyi index 21c8a5cd4e0c9..68ada65693485 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/sched.pyi b/mypy/typeshed/stdlib/sched.pyi index a8ec78d68fd24..75dd63d0414af 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 412fd71ee38dc..c86d20c352e06 100644 --- a/mypy/typeshed/stdlib/select.pyi +++ b/mypy/typeshed/stdlib/select.pyi @@ -110,7 +110,7 @@ if sys.platform == "linux": def __exit__( self, __exc_type: type[BaseException] | None = None, - __exc_val: BaseException | None = ..., + __exc_value: BaseException | None = ..., __exc_tb: TracebackType | None = None, ) -> None: ... def close(self) -> None: ... diff --git a/mypy/typeshed/stdlib/shlex.pyi b/mypy/typeshed/stdlib/shlex.pyi index fa04932db6768..c4fd23d606664 100644 --- a/mypy/typeshed/stdlib/shlex.pyi +++ b/mypy/typeshed/stdlib/shlex.pyi @@ -29,7 +29,7 @@ class shlex(Iterable[str]): debug: int lineno: int token: str - eof: str + eof: str | None @property def punctuation_chars(self) -> str: ... def __init__( @@ -39,12 +39,12 @@ class shlex(Iterable[str]): 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 read_token(self) -> str | None: ... + def sourcehook(self, newfile: str) -> tuple[str, TextIO] | None: ... def push_source(self, newstream: str | TextIO, 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 0e4f521e5e34d..38c50d51b1299 100644 --- a/mypy/typeshed/stdlib/shutil.pyi +++ b/mypy/typeshed/stdlib/shutil.pyi @@ -2,6 +2,7 @@ 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 @@ -87,22 +88,46 @@ else: ignore_dangling_symlinks: bool = False, ) -> _PathReturn: ... -_OnErrorCallback: TypeAlias = Callable[[Callable[..., Any], Any, Any], object] +_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 = ..., - onerror: _OnErrorCallback | None = ..., + ignore_errors: bool = False, + onerror: _OnErrorCallback | None = None, *, - dir_fd: int | None = ..., + onexc: None = None, + dir_fd: int | None = None, + ) -> None: ... + @overload + def __call__( + self, + path: StrOrBytesPath, + ignore_errors: bool = False, + onerror: None = None, + *, + 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 @@ -167,7 +192,15 @@ 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: ... + +if sys.version_info >= (3, 8): + def unpack_archive( + filename: StrPath, extract_dir: StrPath | None = None, format: str | None = None, *, filter: _TarfileFilter | None = None + ) -> None: ... + +else: + def unpack_archive(filename: StrPath, extract_dir: StrPath | None = None, format: str | None = None) -> None: ... + @overload def register_unpack_format( name: str, @@ -182,4 +215,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 e411d47016b69..4c961a0c9aabd 100644 --- a/mypy/typeshed/stdlib/signal.pyi +++ b/mypy/typeshed/stdlib/signal.pyi @@ -53,6 +53,8 @@ class Signals(IntEnum): SIGPWR: int SIGRTMAX: int SIGRTMIN: int + if sys.version_info >= (3, 11): + SIGSTKFLT: int class Handlers(IntEnum): SIG_DFL: int @@ -147,6 +149,8 @@ else: 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): diff --git a/mypy/typeshed/stdlib/smtplib.pyi b/mypy/typeshed/stdlib/smtplib.pyi index 0d7595fc1d6d5..584fa164fec94 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 @@ -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 @@ -133,17 +133,17 @@ class SMTP: 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: ... diff --git a/mypy/typeshed/stdlib/socket.pyi b/mypy/typeshed/stdlib/socket.pyi index dbc1d46ec1d44..5dd92ec8e1161 100644 --- a/mypy/typeshed/stdlib/socket.pyi +++ b/mypy/typeshed/stdlib/socket.pyi @@ -438,6 +438,38 @@ if sys.platform == "win32": SIO_LOOPBACK_FAST_PATH as SIO_LOOPBACK_FAST_PATH, SIO_RCVALL as SIO_RCVALL, ) +if sys.version_info >= (3, 12): + from _socket import ( + 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.version_info >= (3, 11) and sys.platform == "darwin": + from _socket import TCP_CONNECTION_INFO as TCP_CONNECTION_INFO # Re-exported from errno EBADF: int @@ -489,6 +521,8 @@ class AddressFamily(IntEnum): AF_LINK: int if sys.platform != "darwin": AF_BLUETOOTH: int + if sys.platform == "win32" and sys.version_info >= (3, 12): + AF_HYPERV: int AF_INET = AddressFamily.AF_INET AF_INET6 = AddressFamily.AF_INET6 @@ -540,6 +574,9 @@ if sys.platform != "win32" or sys.version_info >= (3, 9): if sys.platform != "darwin": AF_BLUETOOTH = AddressFamily.AF_BLUETOOTH +if sys.platform == "win32" and sys.version_info >= (3, 12): + AF_HYPERV = AddressFamily.AF_HYPERV + class SocketKind(IntEnum): SOCK_STREAM: int SOCK_DGRAM: int @@ -664,7 +701,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,9 +762,9 @@ 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: ... diff --git a/mypy/typeshed/stdlib/socketserver.pyi b/mypy/typeshed/stdlib/socketserver.pyi index 3f0bb0eea0ce1..6a932f66cd09a 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, @@ -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/sqlite3/dbapi2.pyi b/mypy/typeshed/stdlib/sqlite3/dbapi2.pyi index 26188445547ef..24974f787c62d 100644 --- a/mypy/typeshed/stdlib/sqlite3/dbapi2.pyi +++ b/mypy/typeshed/stdlib/sqlite3/dbapi2.pyi @@ -340,7 +340,7 @@ 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 enable_load_extension(self, __enable: bool) -> None: ... def load_extension(self, __name: str) -> None: ... def backup( self, @@ -377,7 +377,7 @@ class Cursor(Iterator[Any]): def rowcount(self) -> int: ... def __init__(self, __cursor: Connection) -> None: ... def close(self) -> None: ... - def execute(self, __sql: str, __parameters: _Parameters = ...) -> Self: ... + 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]: ... @@ -390,14 +390,13 @@ class Cursor(Iterator[Any]): 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): ... @@ -417,18 +416,18 @@ class Row: 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 __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: ... + 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: ... if sys.version_info >= (3, 8): @final @@ -453,6 +452,6 @@ if sys.version_info >= (3, 11): 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/ssl.pyi b/mypy/typeshed/stdlib/ssl.pyi index bbf8a4c6d65ab..20b8802bd7b9f 100644 --- a/mypy/typeshed/stdlib/ssl.pyi +++ b/mypy/typeshed/stdlib/ssl.pyi @@ -94,8 +94,8 @@ 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: ... +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: ... @@ -291,11 +291,14 @@ 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 @@ -467,7 +470,7 @@ class MemoryBIO: pending: int eof: bool def read(self, __size: int = -1) -> bytes: ... - def write(self, __buf: ReadableBuffer) -> int: ... + def write(self, __b: ReadableBuffer) -> int: ... def write_eof(self) -> None: ... @final diff --git a/mypy/typeshed/stdlib/statistics.pyi b/mypy/typeshed/stdlib/statistics.pyi index 1358b1f90d7d1..af5fcec6ad0cb 100644 --- a/mypy/typeshed/stdlib/statistics.pyi +++ b/mypy/typeshed/stdlib/statistics.pyi @@ -114,8 +114,15 @@ if sys.version_info >= (3, 8): def __rsub__(self, x2: float | NormalDist) -> NormalDist: ... __rmul__ = __mul__ -if sys.version_info >= (3, 10): +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 covariance(__x: Sequence[_Number], __y: Sequence[_Number]) -> float: ... class LinearRegression(NamedTuple): diff --git a/mypy/typeshed/stdlib/string.pyi b/mypy/typeshed/stdlib/string.pyi index dc9a449e0e399..1a875a071bf57 100644 --- a/mypy/typeshed/stdlib/string.pyi +++ b/mypy/typeshed/stdlib/string.pyi @@ -47,8 +47,8 @@ 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: ... diff --git a/mypy/typeshed/stdlib/subprocess.pyi b/mypy/typeshed/stdlib/subprocess.pyi index 3940fad7b9159..346e4d5513d85 100644 --- a/mypy/typeshed/stdlib/subprocess.pyi +++ b/mypy/typeshed/stdlib/subprocess.pyi @@ -101,158 +101,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 +260,48 @@ if sys.version_info >= (3, 11): encoding: None = None, errors: None = None, input: ReadableBuffer | None = None, - text: Literal[None, False] = ..., + text: Literal[None, False] = 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 +309,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 +464,46 @@ elif sys.version_info >= (3, 10): encoding: None = None, errors: None = None, input: ReadableBuffer | None = None, - text: Literal[None, False] = ..., + text: Literal[None, False] = 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 +511,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 +662,71 @@ elif sys.version_info >= (3, 9): encoding: None = None, errors: None = None, input: ReadableBuffer | None = None, - text: Literal[None, False] = ..., + text: Literal[None, False] = 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 +734,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 +841,35 @@ else: encoding: None = None, errors: None = None, input: ReadableBuffer | None = None, - text: Literal[None, False] = ..., + text: Literal[None, False] = 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 +878,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 +993,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 +1108,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[None, False] = 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 +1298,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[None, False] = 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 +1482,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[None, False] = 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[None, False] = 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 +1870,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 +1901,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 +1933,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 +1964,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, @@ -1995,7 +1995,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[None, False] = None, encoding: None = None, @@ -2026,7 +2026,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 +2059,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 +2089,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 +2120,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 +2150,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, @@ -2180,7 +2180,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[None, False] = None, encoding: None = None, @@ -2210,7 +2210,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 +2242,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 +2271,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 +2301,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 +2330,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, @@ -2359,7 +2359,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[None, False] = None, encoding: None = None, @@ -2388,7 +2388,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 +2418,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 +2443,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 +2469,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 +2494,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, @@ -2519,7 +2519,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[None, False] = None, encoding: None = None, @@ -2544,7 +2544,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, diff --git a/mypy/typeshed/stdlib/sys.pyi b/mypy/typeshed/stdlib/sys.pyi index e12881599b4a3..ca049124053aa 100644 --- a/mypy/typeshed/stdlib/sys.pyi +++ b/mypy/typeshed/stdlib/sys.pyi @@ -6,7 +6,7 @@ from importlib.abc import PathEntryFinder from importlib.machinery import ModuleSpec from io import TextIOWrapper from types import FrameType, ModuleType, TracebackType -from typing import Any, NoReturn, Protocol, TextIO, TypeVar, overload +from typing import Any, NoReturn, Protocol, TextIO, TypeVar from typing_extensions import Final, Literal, TypeAlias, final _T = TypeVar("_T") @@ -57,9 +57,20 @@ if sys.version_info >= (3, 8): 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,6 +212,20 @@ 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 + @final class _version_info(_UninstantiableStructseq, tuple[int, int, int, str, int]): @property @@ -221,7 +246,7 @@ def _clear_type_cache() -> None: ... def _current_frames() -> dict[int, FrameType]: ... def _getframe(__depth: int = 0) -> FrameType: ... def _debugmallocstats() -> None: ... -def __displayhook__(__value: object) -> None: ... +def __displayhook__(__object: object) -> None: ... def __excepthook__(__exctype: type[BaseException], __value: BaseException, __traceback: TracebackType | None) -> None: ... def exc_info() -> OptExcInfo: ... @@ -239,10 +264,7 @@ def getfilesystemencoding() -> str: ... def getfilesystemencodeerrors() -> str: ... 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: ... @@ -299,7 +321,7 @@ if sys.version_info < (3, 9): 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: + class UnraisableHookArgs(Protocol): exc_type: type[BaseException] exc_value: BaseException | None exc_traceback: TracebackType | None @@ -337,3 +359,13 @@ if sys.version_info < (3, 8): # 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: ... diff --git a/mypy/typeshed/stdlib/tarfile.pyi b/mypy/typeshed/stdlib/tarfile.pyi index 5cf1d55cac63e..d9d9641ac6983 100644 --- a/mypy/typeshed/stdlib/tarfile.pyi +++ b/mypy/typeshed/stdlib/tarfile.pyi @@ -7,7 +7,7 @@ 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_extensions import Literal, Self, TypeAlias __all__ = [ "TarFile", @@ -26,6 +26,21 @@ __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: ... @@ -125,6 +140,7 @@ class TarFile: debug: int | None errorlevel: int | None offset: int # undocumented + extraction_filter: _FilterFunction | None def __init__( self, name: StrOrBytesPath | None = None, @@ -275,12 +291,32 @@ class TarFile: def getnames(self) -> _list[str]: ... def list(self, verbose: bool = True, *, members: _list[TarInfo] | None = None) -> None: ... def next(self) -> TarInfo | None: ... - def extractall( - self, path: StrOrBytesPath = ".", members: Iterable[TarInfo] | None = None, *, numeric_owner: bool = False - ) -> None: ... - def extract( - self, member: str | TarInfo, path: StrOrBytesPath = "", set_attrs: bool = True, *, numeric_owner: bool = False - ) -> None: ... + if sys.version_info >= (3, 8): + def extractall( + self, + path: StrOrBytesPath = ".", + members: Iterable[TarInfo] | None = None, + *, + numeric_owner: bool = False, + filter: _TarfileFilter | None = ..., + ) -> None: ... + def extract( + self, + member: str | TarInfo, + path: StrOrBytesPath = "", + set_attrs: bool = True, + *, + numeric_owner: bool = False, + filter: _TarfileFilter | None = ..., + ) -> None: ... + else: + def extractall( + self, path: StrOrBytesPath = ".", members: Iterable[TarInfo] | None = None, *, numeric_owner: bool = False + ) -> None: ... + def extract( + self, member: str | TarInfo, path: StrOrBytesPath = "", set_attrs: bool = True, *, numeric_owner: bool = False + ) -> None: ... + def _extract_member( self, tarinfo: TarInfo, targetpath: str, set_attrs: bool = True, numeric_owner: bool = False ) -> None: ... # undocumented @@ -324,6 +360,31 @@ class StreamError(TarError): ... class ExtractError(TarError): ... class HeaderError(TarError): ... +if sys.version_info >= (3, 8): + 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,6 +414,21 @@ class TarInfo: def linkpath(self) -> str: ... @linkpath.setter def linkpath(self, linkname: str) -> None: ... + if sys.version_info >= (3, 8): + 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: ... diff --git a/mypy/typeshed/stdlib/tempfile.pyi b/mypy/typeshed/stdlib/tempfile.pyi index dbff6d632d02e..ea04303683b5f 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_extensions import Literal, Self if sys.version_info >= (3, 9): from types import GenericAlias @@ -30,13 +41,54 @@ 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, 12): + @overload + def NamedTemporaryFile( + 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, + delete: bool = True, + *, + errors: str | None = None, + delete_on_close: bool = True, + ) -> _TemporaryFileWrapper[str]: ... + @overload + def NamedTemporaryFile( + mode: OpenBinaryMode = "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, + delete: bool = True, + *, + errors: str | None = None, + delete_on_close: bool = True, + ) -> _TemporaryFileWrapper[bytes]: ... + @overload + def NamedTemporaryFile( + 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, + delete: bool = True, + *, + errors: str | None = None, + delete_on_close: bool = True, + ) -> _TemporaryFileWrapper[Any]: ... -if sys.version_info >= (3, 8): +elif sys.version_info >= (3, 8): @overload def NamedTemporaryFile( - mode: _StrMode, + mode: OpenTextMode, buffering: int = -1, encoding: str | None = None, newline: str | None = None, @@ -49,7 +101,7 @@ if sys.version_info >= (3, 8): ) -> _TemporaryFileWrapper[str]: ... @overload def NamedTemporaryFile( - mode: _BytesMode = "w+b", + mode: OpenBinaryMode = "w+b", buffering: int = -1, encoding: str | None = None, newline: str | None = None, @@ -77,7 +129,7 @@ if sys.version_info >= (3, 8): else: @overload def NamedTemporaryFile( - mode: _StrMode, + mode: OpenTextMode, buffering: int = -1, encoding: str | None = None, newline: str | None = None, @@ -88,7 +140,7 @@ else: ) -> _TemporaryFileWrapper[str]: ... @overload def NamedTemporaryFile( - mode: _BytesMode = "w+b", + mode: OpenBinaryMode = "w+b", buffering: int = -1, encoding: str | None = None, newline: str | None = None, @@ -112,10 +164,11 @@ else: if sys.platform == "win32": TemporaryFile = NamedTemporaryFile else: + # See the comments for builtins.open() for an explanation of the overloads. if sys.version_info >= (3, 8): @overload def TemporaryFile( - mode: _StrMode, + mode: OpenTextMode, buffering: int = -1, encoding: str | None = None, newline: str | None = None, @@ -124,11 +177,11 @@ else: dir: GenericPath[AnyStr] | None = None, *, errors: str | None = None, - ) -> IO[str]: ... + ) -> io.TextIOWrapper: ... @overload def TemporaryFile( - mode: _BytesMode = "w+b", - buffering: int = -1, + mode: OpenBinaryMode, + buffering: Literal[0], encoding: str | None = None, newline: str | None = None, suffix: AnyStr | None = None, @@ -136,7 +189,54 @@ else: dir: GenericPath[AnyStr] | None = None, *, errors: str | None = None, - ) -> IO[bytes]: ... + ) -> 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", @@ -152,40 +252,84 @@ else: 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]: ... + 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, + ) -> 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, + ) -> 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, + ) -> io.FileIO: ... + @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, + ) -> io.BufferedRandom: ... @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]: ... + 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, + ) -> io.BufferedWriter: ... @overload def TemporaryFile( - mode: str = ..., - buffering: int = ..., - encoding: str | None = ..., - newline: str | None = ..., - suffix: AnyStr | None = ..., - prefix: AnyStr | None = ..., - dir: GenericPath[AnyStr] | None = ..., + 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, + ) -> io.BufferedReader: ... + @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, ) -> IO[Any]: ... class _TemporaryFileWrapper(Generic[AnyStr], 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 +359,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,6 +380,7 @@ 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 @@ -236,7 +391,7 @@ class SpooledTemporaryFile(IO[AnyStr], _SpooledTemporaryFileBase): def __init__( self: SpooledTemporaryFile[bytes], max_size: int = 0, - mode: _BytesMode = "w+b", + mode: OpenBinaryMode = "w+b", buffering: int = -1, encoding: str | None = None, newline: str | None = None, @@ -250,7 +405,7 @@ class SpooledTemporaryFile(IO[AnyStr], _SpooledTemporaryFileBase): def __init__( self: SpooledTemporaryFile[str], max_size: int, - mode: _StrMode, + mode: OpenTextMode, buffering: int = -1, encoding: str | None = None, newline: str | None = None, @@ -265,7 +420,7 @@ class SpooledTemporaryFile(IO[AnyStr], _SpooledTemporaryFileBase): self: SpooledTemporaryFile[str], max_size: int = 0, *, - mode: _StrMode, + mode: OpenTextMode, buffering: int = -1, encoding: str | None = None, newline: str | None = None, @@ -309,7 +464,7 @@ class SpooledTemporaryFile(IO[AnyStr], _SpooledTemporaryFileBase): def __init__( self: SpooledTemporaryFile[bytes], max_size: int = 0, - mode: _BytesMode = "w+b", + mode: OpenBinaryMode = "w+b", buffering: int = -1, encoding: str | None = None, newline: str | None = None, @@ -321,7 +476,7 @@ class SpooledTemporaryFile(IO[AnyStr], _SpooledTemporaryFileBase): def __init__( self: SpooledTemporaryFile[str], max_size: int, - mode: _StrMode, + mode: OpenTextMode, buffering: int = -1, encoding: str | None = None, newline: str | None = None, @@ -334,7 +489,7 @@ class SpooledTemporaryFile(IO[AnyStr], _SpooledTemporaryFileBase): self: SpooledTemporaryFile[str], max_size: int = 0, *, - mode: _StrMode, + mode: OpenTextMode, buffering: int = -1, encoding: str | None = None, newline: str | None = None, @@ -392,8 +547,18 @@ class SpooledTemporaryFile(IO[AnyStr], _SpooledTemporaryFileBase): def seek(self, offset: int, whence: int = ...) -> int: ... def tell(self) -> int: ... 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 +570,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], diff --git a/mypy/typeshed/stdlib/textwrap.pyi b/mypy/typeshed/stdlib/textwrap.pyi index e4a5b7899e8eb..c00cce3c2d577 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 c0b344fe757d0..badd09cae0518 100644 --- a/mypy/typeshed/stdlib/threading.pyi +++ b/mypy/typeshed/stdlib/threading.pyi @@ -3,6 +3,7 @@ from _typeshed import ProfileFunction, TraceFunction from collections.abc import Callable, Iterable, Mapping from types import TracebackType from typing import Any, TypeVar +from typing_extensions import final _T = TypeVar("_T") @@ -36,6 +37,9 @@ if sys.version_info >= (3, 8): 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: ... @@ -52,6 +56,10 @@ if sys.version_info >= (3, 8): 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: ... @@ -77,7 +85,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, @@ -101,6 +109,7 @@ class Thread: class _DummyThread(Thread): def __init__(self) -> None: ... +@final class Lock: def __enter__(self) -> bool: ... def __exit__( @@ -110,6 +119,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: ... diff --git a/mypy/typeshed/stdlib/tkinter/__init__.pyi b/mypy/typeshed/stdlib/tkinter/__init__.pyi index 1d30e4b73c235..3291b0c9dd98a 100644 --- a/mypy/typeshed/stdlib/tkinter/__init__.pyi +++ b/mypy/typeshed/stdlib/tkinter/__init__.pyi @@ -6,7 +6,7 @@ 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 import Any, Generic, NamedTuple, Protocol, TypeVar, overload, type_check_only from typing_extensions import Literal, TypeAlias, TypedDict if sys.version_info >= (3, 9): @@ -173,10 +173,7 @@ 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] @@ -405,7 +402,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: ... @@ -504,7 +501,7 @@ class Misc: def grid_columnconfigure( self, index: _GridIndex, - cnf: _GridIndexInfo = ..., + cnf: _GridIndexInfo = {}, *, minsize: _ScreenUnits = ..., pad: _ScreenUnits = ..., @@ -514,7 +511,7 @@ class Misc: def grid_rowconfigure( self, index: _GridIndex, - cnf: _GridIndexInfo = ..., + cnf: _GridIndexInfo = {}, *, minsize: _ScreenUnits = ..., pad: _ScreenUnits = ..., @@ -666,7 +663,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 @@ -745,15 +742,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 = ..., @@ -825,7 +822,7 @@ class Pack: # replaced by **kwargs. def pack_configure( self, - cnf: Mapping[str, Any] | None = ..., + cnf: Mapping[str, Any] | None = {}, *, after: Misc = ..., anchor: _Anchor = ..., @@ -861,7 +858,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 +893,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 +917,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,11 +944,11 @@ class Toplevel(BaseWidget, Wm): 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 = ..., class_: str = ..., @@ -959,8 +956,8 @@ class Toplevel(BaseWidget, Wm): container: bool = ..., cursor: _Cursor = ..., height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., menu: Menu = ..., name: str = ..., @@ -978,15 +975,15 @@ class Toplevel(BaseWidget, 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 = ..., @@ -1003,30 +1000,30 @@ class Button(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 = ..., # 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 + disabledforeground: str = ..., + fg: str = ..., # same as foreground font: _FontDescription = ..., - foreground: _Color = ..., + 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 = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., image: _ImageSpec = ..., justify: Literal["left", "center", "right"] = ..., @@ -1053,26 +1050,26 @@ class Button(Widget): 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"] = ..., @@ -1100,11 +1097,11 @@ class Canvas(Widget, XView, YView): 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 = ..., closeenough: float = ..., @@ -1113,10 +1110,10 @@ class Canvas(Widget, XView, YView): # 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 = ..., + insertbackground: str = ..., insertborderwidth: _ScreenUnits = ..., insertofftime: int = ..., insertontime: int = ..., @@ -1127,9 +1124,9 @@ class Canvas(Widget, XView, YView): # 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 = ..., + selectbackground: str = ..., selectborderwidth: _ScreenUnits = ..., - selectforeground: _Color = ..., + selectforeground: str = ..., # man page says that state can be 'hidden', but it can't state: Literal["normal", "disabled"] = ..., takefocus: _TakeFocusValue = ..., @@ -1144,19 +1141,19 @@ class Canvas(Widget, XView, YView): 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 +1161,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 +1176,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,22 +1210,22 @@ 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, @@ -1244,58 +1234,58 @@ class Canvas(Widget, XView, YView): __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 = ..., + 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], *, - 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 = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_line( self, @@ -1308,29 +1298,29 @@ class Canvas(Widget, XView, YView): | 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 = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_oval( self, @@ -1339,60 +1329,60 @@ class Canvas(Widget, XView, YView): __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 = ..., + outlinestipple: str = ..., state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + 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], *, - 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 = ..., + outlinestipple: str = ..., state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_oval( self, @@ -1405,30 +1395,30 @@ class Canvas(Widget, XView, YView): | 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 = ..., + outlinestipple: str = ..., state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_polygon( self, @@ -1437,66 +1427,66 @@ class Canvas(Widget, XView, YView): __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 = ..., + 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_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 = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_polygon( self, @@ -1509,33 +1499,33 @@ class Canvas(Widget, XView, YView): | 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 = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_rectangle( self, @@ -1544,60 +1534,60 @@ class Canvas(Widget, XView, YView): __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 = ..., + outlinestipple: str = ..., state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + 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], *, - 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 = ..., + outlinestipple: str = ..., state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_rectangle( self, @@ -1610,71 +1600,71 @@ class Canvas(Widget, XView, YView): | 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 = ..., + outlinestipple: str = ..., state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_text( self, __x: float, __y: float, *, - activefill: _Color = ..., + activefill: str = ..., activestipple: str = ..., anchor: _Anchor = ..., - disabledfill: _Color = ..., - disabledstipple: _Bitmap = ..., - fill: _Color = ..., + disabledfill: str = ..., + disabledstipple: str = ..., + fill: str = ..., font: _FontDescription = ..., justify: Literal["left", "center", "right"] = ..., offset: _ScreenUnits = ..., state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + 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], *, - activefill: _Color = ..., + activefill: str = ..., activestipple: str = ..., anchor: _Anchor = ..., - disabledfill: _Color = ..., - disabledstipple: _Bitmap = ..., - fill: _Color = ..., + disabledfill: str = ..., + disabledstipple: str = ..., + fill: str = ..., font: _FontDescription = ..., justify: Literal["left", "center", "right"] = ..., offset: _ScreenUnits = ..., state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., text: float | str = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_window( self, @@ -1687,7 +1677,7 @@ class Canvas(Widget, XView, YView): tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., window: Widget = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_window( self, @@ -1699,39 +1689,39 @@ class Canvas(Widget, XView, YView): 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: ... @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 moveto(self, tagOrId: str | int, x: Literal[""] | float = "", y: Literal[""] | float = "") -> None: ... - def postscript(self, cnf=..., **kw): ... + 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 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, *args) -> None: ... def scan_mark(self, x, y) -> None: ... def scan_dragto(self, x, y, gain: int = 10) -> None: ... @@ -1746,27 +1736,27 @@ class Checkbutton(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 = ..., 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 = ..., @@ -1789,7 +1779,7 @@ class Checkbutton(Widget): padx: _ScreenUnits = ..., pady: _ScreenUnits = ..., relief: _Relief = ..., - selectcolor: _Color = ..., + selectcolor: str = ..., selectimage: _ImageSpec = ..., state: Literal["normal", "active", "disabled"] = ..., takefocus: _TakeFocusValue = ..., @@ -1807,25 +1797,25 @@ class Checkbutton(Widget): 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 = ..., @@ -1837,7 +1827,7 @@ class Checkbutton(Widget): padx: _ScreenUnits = ..., pady: _ScreenUnits = ..., relief: _Relief = ..., - selectcolor: _Color = ..., + selectcolor: str = ..., selectimage: _ImageSpec = ..., state: Literal["normal", "active", "disabled"] = ..., takefocus: _TakeFocusValue = ..., @@ -1865,24 +1855,24 @@ 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 = ..., + 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 = ..., @@ -1891,11 +1881,11 @@ class Entry(Widget, XView): invcmd: _EntryValidateCommand = ..., # same as invalidcommand justify: Literal["left", "center", "right"] = ..., name: str = ..., - 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 = ..., @@ -1911,22 +1901,22 @@ class Entry(Widget, XView): 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 +1924,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 = ..., @@ -1976,11 +1966,11 @@ class Frame(Widget): 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 = ..., class_: str = ..., # can't be changed with configure() @@ -1988,8 +1978,8 @@ class Frame(Widget): container: bool = ..., # can't be changed with configure() cursor: _Cursor = ..., height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., name: str = ..., padx: _ScreenUnits = ..., @@ -2004,15 +1994,15 @@ class Frame(Widget): 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,26 +2018,26 @@ class Label(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 = ..., - 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"] = ..., @@ -2068,24 +2058,24 @@ class Label(Widget): 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,23 +2098,23 @@ 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 = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., cursor: _Cursor = ..., - disabledforeground: _Color = ..., + disabledforeground: str = ..., exportselection: int = ..., - 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"] = ..., # There's no tkinter.ListVar, but seems like bare tkinter.Variable @@ -2139,9 +2129,9 @@ class Listbox(Widget, XView, YView): listvariable: Variable = ..., name: str = ..., relief: _Relief = ..., - selectbackground: _Color = ..., + selectbackground: str = ..., selectborderwidth: _ScreenUnits = ..., - selectforeground: _Color = ..., + selectforeground: str = ..., # from listbox man page: "The value of the [selectmode] option may be # arbitrary, but the default bindings expect it to be ..." # @@ -2161,27 +2151,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,25 +2211,25 @@ 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 = ..., + disabledforeground: str = ..., + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., name: str = ..., postcommand: Callable[[], object] | str = ..., relief: _Relief = ..., - selectcolor: _Color = ..., + selectcolor: str = ..., takefocus: _TakeFocusValue = ..., tearoff: int = ..., # I guess tearoffcommand arguments are supposed to be widget objects, @@ -2254,22 +2244,22 @@ class Menu(Widget): 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 +2271,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 +2296,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 +2322,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 +2342,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 +2391,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 +2418,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 +2439,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,27 +2481,27 @@ 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 = ..., + 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 = ..., @@ -2534,25 +2524,25 @@ class Menubutton(Widget): 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,21 +2567,21 @@ class Message(Widget): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, 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"] = ..., name: str = ..., @@ -2611,17 +2601,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,27 +2630,27 @@ class Radiobutton(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 = ..., 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 = ..., @@ -2671,7 +2661,7 @@ class Radiobutton(Widget): padx: _ScreenUnits = ..., pady: _ScreenUnits = ..., relief: _Relief = ..., - selectcolor: _Color = ..., + selectcolor: str = ..., selectimage: _ImageSpec = ..., state: Literal["normal", "active", "disabled"] = ..., takefocus: _TakeFocusValue = ..., @@ -2690,25 +2680,25 @@ class Radiobutton(Widget): 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 = ..., @@ -2718,7 +2708,7 @@ class Radiobutton(Widget): padx: _ScreenUnits = ..., pady: _ScreenUnits = ..., relief: _Relief = ..., - selectcolor: _Color = ..., + selectcolor: str = ..., selectimage: _ImageSpec = ..., state: Literal["normal", "active", "disabled"] = ..., takefocus: _TakeFocusValue = ..., @@ -2744,12 +2734,12 @@ class Scale(Widget): 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 = ..., bigincrement: float = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., @@ -2757,12 +2747,12 @@ class Scale(Widget): 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 = ..., @@ -2779,7 +2769,7 @@ class Scale(Widget): takefocus: _TakeFocusValue = ..., tickinterval: float = ..., to: float = ..., - troughcolor: _Color = ..., + troughcolor: str = ..., variable: IntVar | DoubleVar = ..., width: _ScreenUnits = ..., ) -> None: ... @@ -2788,22 +2778,22 @@ class Scale(Widget): 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 +2809,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,13 +2825,13 @@ class Scrollbar(Widget): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, - activebackground: _Color = ..., + activebackground: str = ..., activerelief: _Relief = ..., - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., # There are many ways how the command may get called. Search for @@ -2851,8 +2841,8 @@ class Scrollbar(Widget): command: Callable[..., tuple[float, float] | None] | str = ..., cursor: _Cursor = ..., elementborderwidth: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., jump: bool = ..., name: str = ..., @@ -2861,7 +2851,7 @@ class Scrollbar(Widget): repeatdelay: int = ..., repeatinterval: int = ..., takefocus: _TakeFocusValue = ..., - troughcolor: _Color = ..., + troughcolor: str = ..., width: _ScreenUnits = ..., ) -> None: ... @overload @@ -2869,18 +2859,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 +2878,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 @@ -2907,30 +2897,30 @@ 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 = ..., + 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 = ..., # 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 = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., - inactiveselectbackground: _Color = ..., - insertbackground: _Color = ..., + inactiveselectbackground: str = ..., + insertbackground: str = ..., insertborderwidth: _ScreenUnits = ..., insertofftime: int = ..., insertontime: int = ..., @@ -2941,9 +2931,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 = ..., @@ -2966,24 +2956,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 +2983,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 = ..., @@ -3078,7 +3068,7 @@ class Text(Widget, XView, YView): # 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_create(self, index, cnf={}, **kw): ... def image_names(self): ... def index(self, index: _TextIndex) -> str: ... def insert(self, index: _TextIndex, chars: str, *args: str | list[str] | tuple[str, ...]) -> None: ... @@ -3092,7 +3082,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,33 +3122,33 @@ 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 @@ -3181,7 +3171,7 @@ class Text(Widget, XView, YView): def window_cget(self, index, option): ... def window_configure(self, index, cnf: Incomplete | None = None, **kw): ... window_config = window_configure - def window_create(self, index, cnf=..., **kw) -> None: ... + def window_create(self, index, cnf={}, **kw) -> None: ... def window_names(self): ... def yview_pickplace(self, *what): ... # deprecated @@ -3206,16 +3196,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: ... + +@type_check_only +class _BitmapImageLike(_Image): ... + +@type_check_only +class _PhotoImageLike(_Image): ... -class 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 +3223,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,11 +3263,11 @@ 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: ... @@ -3278,17 +3276,18 @@ class PhotoImage(Image): 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,34 +3299,34 @@ 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 = ..., + 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 = ..., + 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 = ..., @@ -3336,13 +3335,13 @@ class Spinbox(Widget, XView): invcmd: _EntryValidateCommand = ..., justify: Literal["left", "center", "right"] = ..., name: str = ..., - 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 = ..., @@ -3360,31 +3359,31 @@ class Spinbox(Widget, XView): 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 +3391,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 = ..., @@ -3440,23 +3439,23 @@ class LabelFrame(Widget): 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 = ..., 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 = ..., + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., # '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"] = ..., @@ -3475,18 +3474,18 @@ class LabelFrame(Widget): 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,11 +3504,11 @@ class PanedWindow(Widget): 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 = ..., @@ -3519,7 +3518,7 @@ class PanedWindow(Widget): name: str = ..., opaqueresize: bool = ..., orient: Literal["horizontal", "vertical"] = ..., - proxybackground: _Color = ..., + proxybackground: str = ..., proxyborderwidth: _ScreenUnits = ..., proxyrelief: _Relief = ..., relief: _Relief = ..., @@ -3535,9 +3534,9 @@ class PanedWindow(Widget): 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 +3545,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 4300d94f58e8b..09bc8cbb4f1ee 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/dialog.pyi b/mypy/typeshed/stdlib/tkinter/dialog.pyi index 8825188c767ee..7bc77ac6d8b54 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: Incomplete) -> None: ... def destroy(self) -> None: ... diff --git a/mypy/typeshed/stdlib/tkinter/simpledialog.pyi b/mypy/typeshed/stdlib/tkinter/simpledialog.pyi index 2c57cce7371c8..45dce21a6b1c3 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 5dd6f040fab71..672c5ab674033 100644 --- a/mypy/typeshed/stdlib/tkinter/tix.pyi +++ b/mypy/typeshed/stdlib/tkinter/tix.pyi @@ -54,117 +54,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 +177,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 +204,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 +228,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 +236,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 +246,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 +257,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 +287,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 61ebc0e2734fe..009fdf51a440d 100644 --- a/mypy/typeshed/stdlib/tkinter/ttk.pyi +++ b/mypy/typeshed/stdlib/tkinter/ttk.pyi @@ -177,12 +177,12 @@ class Entry(Widget, tkinter.Entry): master: tkinter.Misc | None = None, widget: str | None = None, *, - background: tkinter._Color = ..., # undocumented + background: str = ..., # undocumented class_: str = ..., cursor: tkinter._Cursor = ..., exportselection: bool = ..., font: _FontDescription = ..., - foreground: tkinter._Color = ..., + foreground: str = ..., invalidcommand: tkinter._EntryValidateCommand = ..., justify: Literal["left", "center", "right"] = ..., name: str = ..., @@ -201,11 +201,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 = ..., @@ -226,11 +226,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,12 +254,12 @@ class Combobox(Entry): self, master: tkinter.Misc | None = None, *, - background: tkinter._Color = ..., # undocumented + background: str = ..., # undocumented class_: str = ..., cursor: tkinter._Cursor = ..., exportselection: bool = ..., font: _FontDescription = ..., # undocumented - foreground: tkinter._Color = ..., # undocumented + foreground: str = ..., # undocumented height: int = ..., invalidcommand: tkinter._EntryValidateCommand = ..., # undocumented justify: Literal["left", "center", "right"] = ..., @@ -281,11 +281,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 +309,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"] = ..., @@ -376,14 +376,14 @@ 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 = ..., font: _FontDescription = ..., - foreground: tkinter._Color = ..., + foreground: str = ..., image: tkinter._ImageSpec = ..., justify: Literal["left", "center", "right"] = ..., name: str = ..., @@ -404,13 +404,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 = ..., @@ -865,13 +865,13 @@ class Spinbox(Entry): self, master: tkinter.Misc | None = None, *, - background: tkinter._Color = ..., # undocumented + 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 + foreground: str = ..., # undocumented format: str = ..., from_: float = ..., increment: float = ..., @@ -896,12 +896,12 @@ class Spinbox(Entry): 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 +934,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 @@ -1130,7 +1130,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,8 +1142,8 @@ 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 diff --git a/mypy/typeshed/stdlib/token.pyi b/mypy/typeshed/stdlib/token.pyi index fcd6ef87d2171..85867a2b97446 100644 --- a/mypy/typeshed/stdlib/token.pyi +++ b/mypy/typeshed/stdlib/token.pyi @@ -73,6 +73,9 @@ if sys.version_info >= (3, 8): 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 @@ -145,6 +148,12 @@ if sys.version_info >= (3, 8): 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 ba57402fb8455..0028ed034ae60 100644 --- a/mypy/typeshed/stdlib/tokenize.pyi +++ b/mypy/typeshed/stdlib/tokenize.pyi @@ -83,6 +83,9 @@ if sys.version_info >= (3, 8): if sys.version_info >= (3, 10): __all__ += ["SOFT_KEYWORD"] +if sys.version_info >= (3, 12): + __all__ += ["EXCLAMATION", "FSTRING_END", "FSTRING_MIDDLE", "FSTRING_START"] + if sys.version_info >= (3, 8): from token import EXACT_TOKEN_TYPES as EXACT_TOKEN_TYPES else: diff --git a/mypy/typeshed/stdlib/trace.pyi b/mypy/typeshed/stdlib/trace.pyi index f79b38f1ce828..3764a5b060243 100644 --- a/mypy/typeshed/stdlib/trace.pyi +++ b/mypy/typeshed/stdlib/trace.pyi @@ -34,8 +34,8 @@ class Trace: 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, diff --git a/mypy/typeshed/stdlib/traceback.pyi b/mypy/typeshed/stdlib/traceback.pyi index 4483a8c2a1b0b..a6d6d3e168b3b 100644 --- a/mypy/typeshed/stdlib/traceback.pyi +++ b/mypy/typeshed/stdlib/traceback.pyi @@ -132,12 +132,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 +157,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 +176,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: ... diff --git a/mypy/typeshed/stdlib/turtle.pyi b/mypy/typeshed/stdlib/turtle.pyi index 8017c8290fb9d..5df3e4b90cb56 100644 --- a/mypy/typeshed/stdlib/turtle.pyi +++ b/mypy/typeshed/stdlib/turtle.pyi @@ -405,7 +405,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: ... @@ -674,7 +676,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 d529b3d9ad1a7..e5468ce4ed3c9 100644 --- a/mypy/typeshed/stdlib/types.pyi +++ b/mypy/typeshed/stdlib/types.pyi @@ -17,7 +17,7 @@ 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_extensions import Literal, ParamSpec, Self, TypeVarTuple, final __all__ = [ "FunctionType", @@ -56,15 +56,15 @@ if sys.version_info >= (3, 9): 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: @@ -91,6 +91,8 @@ 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__( @@ -103,9 +105,9 @@ class FunctionType: ) -> None: ... 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 @@ -346,46 +348,54 @@ class ModuleType: # using `builtins.__import__` or `importlib.import_module` less painful def __getattr__(self, name: str) -> 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[_T_co, _T_contra, _V_co]): +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: ... + ) -> _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: ... + ) -> _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: ... @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,14 +405,14 @@ 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: ... + ) -> _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: ... class _StaticFunctionType: # Fictional type to correct the type of MethodType.__func__. @@ -454,7 +464,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 +477,8 @@ 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: ... @final class MethodDescriptorType: @@ -479,7 +489,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,7 +500,7 @@ 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: @@ -536,9 +546,9 @@ class GetSetDescriptorType: def __qualname__(self) -> str: ... @property def __objclass__(self) -> type: ... - def __get__(self, __obj: Any, __type: type = ...) -> Any: ... + def __get__(self, __instance: Any, __owner: type | None = None) -> Any: ... def __set__(self, __instance: Any, __value: Any) -> None: ... - def __delete__(self, __obj: Any) -> None: ... + def __delete__(self, __instance: Any) -> None: ... @final class MemberDescriptorType: @@ -548,21 +558,24 @@ class MemberDescriptorType: def __qualname__(self) -> str: ... @property def __objclass__(self) -> type: ... - def __get__(self, __obj: Any, __type: type = ...) -> Any: ... + def __get__(self, __instance: Any, __owner: type | None = None) -> Any: ... def __set__(self, __instance: Any, __value: Any) -> None: ... - def __delete__(self, __obj: 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 @@ -611,5 +624,5 @@ 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: ... diff --git a/mypy/typeshed/stdlib/typing.pyi b/mypy/typeshed/stdlib/typing.pyi index d06b081d3ddcc..d9f9c18b9a7d5 100644 --- a/mypy/typeshed/stdlib/typing.pyi +++ b/mypy/typeshed/stdlib/typing.pyi @@ -2,7 +2,7 @@ import collections # Needed by aliases like DefaultDict, see mypy issue 2986 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, Incomplete, ReadableBuffer, SupportsKeysAndGetItem from abc import ABCMeta, abstractmethod from contextlib import AbstractAsyncContextManager, AbstractContextManager from re import Match as Match, Pattern as Pattern @@ -20,6 +20,11 @@ from types import ( ) from typing_extensions import Never as _Never, ParamSpec as _ParamSpec, final as _final +if sys.version_info >= (3, 10): + from types import UnionType +if sys.version_info >= (3, 9): + from types import GenericAlias + __all__ = [ "AbstractSet", "Any", @@ -131,14 +136,32 @@ Any = object() @_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: ... @@ -162,20 +185,14 @@ _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 @@ -188,36 +205,61 @@ if sys.version_info >= (3, 8): 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: ... if sys.version_info >= (3, 10): + @_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: ... + @_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 @@ -234,7 +276,7 @@ if sys.version_info >= (3, 10): class NewType: def __init__(self, name: str, tp: Any) -> None: ... - def __call__(self, x: _T) -> _T: ... + def __call__(self, __x: _T) -> _T: ... def __or__(self, other: Any) -> _SpecialForm: ... def __ror__(self, other: Any) -> _SpecialForm: ... __supertype__: type @@ -247,14 +289,12 @@ _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]) 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]: ... # Type aliases and type constructors @@ -355,20 +395,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) +_ReturnT_co = TypeVar("_ReturnT_co", covariant=True) + +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: ... + ) -> _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 @@ -383,7 +427,7 @@ class Awaitable(Protocol[_T_co]): @abstractmethod def __await__(self) -> Generator[Any, None, _T_co]: ... -class Coroutine(Awaitable[_V_co], Generic[_T_co, _T_contra, _V_co]): +class Coroutine(Awaitable[_ReturnT_co], Generic[_YieldT_co, _SendT_contra, _ReturnT_co]): __name__: str __qualname__: str @property @@ -395,15 +439,15 @@ class Coroutine(Awaitable[_V_co], Generic[_T_co, _T_contra, _V_co]): @property def cr_running(self) -> bool: ... @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: ... + ) -> _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 +455,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], + Generator[_YieldT_co, _SendT_contra, _ReturnT_co], + Generic[_YieldT_co, _SendT_contra, _ReturnT_co, _S], + metaclass=ABCMeta, ): ... @runtime_checkable @@ -425,18 +472,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]: ... + ) -> 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: ... @@ -588,7 +635,8 @@ class Mapping(Collection[_KT], Generic[_KT, _VT_co]): 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]): @abstractmethod @@ -682,8 +730,22 @@ class IO(Iterator[AnyStr], Generic[AnyStr]): @abstractmethod def writable(self) -> bool: ... @abstractmethod + @overload + def write(self: IO[str], __s: str) -> int: ... + @abstractmethod + @overload + def write(self: IO[bytes], __s: ReadableBuffer) -> int: ... + @abstractmethod + @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: ... @@ -693,7 +755,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 +777,7 @@ class TextIO(IO[str]): @abstractmethod def __enter__(self) -> TextIO: ... -class ByteString(Sequence[int], metaclass=ABCMeta): ... +ByteString: typing_extensions.TypeAlias = bytes | bytearray | memoryview # Functions @@ -745,9 +807,21 @@ else: ) -> dict[str, Any]: ... if sys.version_info >= (3, 8): - def get_origin(tp: Any) -> Any | None: ... 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: ... + @overload def cast(typ: Type[_T], val: Any) -> _T: ... @overload @@ -766,7 +840,8 @@ 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: ... @@ -774,18 +849,21 @@ if sys.version_info >= (3, 11): class NamedTuple(tuple[Any, ...]): if sys.version_info < (3, 8): - _field_types: collections.OrderedDict[str, type] + _field_types: ClassVar[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 + _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: ... + def __init__(self, __typename: str, __fields: None = None, **kwargs: Any) -> None: ... @classmethod - def _make(cls: Type[_T], iterable: Iterable[Any]) -> _T: ... + def _make(cls, iterable: Iterable[Any]) -> typing_extensions.Self: ... if sys.version_info >= (3, 8): def _asdict(self) -> dict[str, Any]: ... else: @@ -801,6 +879,10 @@ 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. @@ -847,3 +929,30 @@ 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(__arg: _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: ... + if sys.version_info >= (3, 10): + 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 bf3892d5709ee..3169dd3050045 100644 --- a/mypy/typeshed/stdlib/typing_extensions.pyi +++ b/mypy/typeshed/stdlib/typing_extensions.pyi @@ -4,36 +4,80 @@ 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 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 +105,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 +129,7 @@ __all__ = [ "runtime_checkable", "Text", "TypeAlias", + "TypeAliasType", "TypeGuard", "TYPE_CHECKING", "Never", @@ -87,8 +139,48 @@ __all__ = [ "clear_overloads", "get_args", "get_origin", + "get_original_bases", "get_overloads", "get_type_hints", + "AbstractSet", + "AnyStr", + "BinaryIO", + "Callable", + "Collection", + "Container", + "Dict", + "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", ] _T = typing.TypeVar("_T") @@ -107,7 +199,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,6 +220,7 @@ 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, ...]] def copy(self) -> Self: ... # Using Never so that only calls using mypy plugin hook that specialize the signature # can go through. @@ -155,6 +248,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,10 +270,11 @@ 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, + NewType as NewType, ParamSpecArgs as ParamSpecArgs, ParamSpecKwargs as ParamSpecKwargs, TypeAlias as TypeAlias, @@ -176,12 +282,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,6 +299,11 @@ else: TypeGuard: _SpecialForm def is_typeddict(tp: object) -> bool: ... + class NewType: + def __init__(self, name: str, tp: Any) -> None: ... + def __call__(self, __x: _T) -> _T: ... + __supertype__: type + # New things in 3.11 # NamedTuples are not new, but the ability to create generic NamedTuples is new in 3.11 if sys.version_info >= (3, 11): @@ -209,7 +324,7 @@ if sys.version_info >= (3, 11): ) else: Self: _SpecialForm - Never: _SpecialForm = ... + Never: _SpecialForm def reveal_type(__obj: _T) -> _T: ... def assert_never(__arg: Never) -> Never: ... def assert_type(__val: _T, __typ: Any) -> _T: ... @@ -226,18 +341,19 @@ 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] + _field_types: ClassVar[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 + _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 @@ -253,16 +369,24 @@ else: # 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, @@ -281,11 +405,18 @@ class TypeVar: @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 +433,51 @@ 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: ... +def deprecated(__msg: str, *, category: type[Warning] | None = ..., stacklevel: int = 1) -> Callable[[_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]: ... diff --git a/mypy/typeshed/stdlib/unittest/case.pyi b/mypy/typeshed/stdlib/unittest/case.pyi index 8f8cf43385a84..45c39e3f3010b 100644 --- a/mypy/typeshed/stdlib/unittest/case.pyi +++ b/mypy/typeshed/stdlib/unittest/case.pyi @@ -48,7 +48,7 @@ else: 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): @@ -68,10 +68,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] @@ -107,8 +110,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 @@ -129,7 +132,7 @@ class TestCase: # 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], @@ -141,7 +144,7 @@ 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], @@ -154,7 +157,7 @@ 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], @@ -166,7 +169,7 @@ 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], diff --git a/mypy/typeshed/stdlib/unittest/mock.pyi b/mypy/typeshed/stdlib/unittest/mock.pyi index f0345c903a3bc..db1cc7d9bfc9b 100644 --- a/mypy/typeshed/stdlib/unittest/mock.pyi +++ b/mypy/typeshed/stdlib/unittest/mock.pyi @@ -3,13 +3,14 @@ from collections.abc import Awaitable, Callable, Coroutine, Iterable, Mapping, S 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_extensions import Final, Literal, 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]]) +_P = ParamSpec("_P") if sys.version_info >= (3, 8): __all__ = ( @@ -47,7 +48,8 @@ else: "seal", ) -__version__: Final[str] +if sys.version_info < (3, 9): + __version__: Final[str] FILTER_DIR: Any @@ -67,21 +69,21 @@ _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: ... @@ -232,8 +234,10 @@ 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]: ... + def __call__(self, func: Callable[_P, _R]) -> Callable[_P, _R]: ... if sys.version_info >= (3, 8): def decoration_helper( self, patched: _patch[Any], args: Sequence[Any], keywargs: Any @@ -255,11 +259,27 @@ class _patch(Generic[_T]): def start(self) -> _T: ... def stop(self) -> None: ... +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 make this work: +# @patch("foo") +# def bar(..., mock: MagicMock) -> None: ... +class _patch_default_new(_patch[_Mock]): + @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 +291,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] @@ -305,7 +322,7 @@ class _patcher: autospec: Any | None = ..., new_callable: Any | None = ..., **kwargs: Any, - ) -> _patch[_Mock]: ... + ) -> _patch_default_new: ... @overload @staticmethod def object( # type: ignore[misc] @@ -437,6 +454,6 @@ class PropertyMock(Mock): else: def __get__(self, obj: _T, obj_type: type[_T] | None) -> Self: ... - def __set__(self, obj: Any, value: Any) -> None: ... + def __set__(self, obj: Any, val: Any) -> None: ... def seal(mock: Any) -> None: ... diff --git a/mypy/typeshed/stdlib/unittest/suite.pyi b/mypy/typeshed/stdlib/unittest/suite.pyi index f6b8ef0035184..c10cbc75d7fd8 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/urllib/parse.pyi b/mypy/typeshed/stdlib/urllib/parse.pyi index 50c5d44cdd804..116754091d1a2 100644 --- a/mypy/typeshed/stdlib/urllib/parse.pyi +++ b/mypy/typeshed/stdlib/urllib/parse.pyi @@ -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, @@ -210,14 +196,15 @@ else: 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 09ce27961999a..8f99c58378711 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, diff --git a/mypy/typeshed/stdlib/uuid.pyi b/mypy/typeshed/stdlib/uuid.pyi index 2492577836264..74ce4ebd6b473 100644 --- a/mypy/typeshed/stdlib/uuid.pyi +++ b/mypy/typeshed/stdlib/uuid.pyi @@ -73,9 +73,20 @@ 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: ... + +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 +96,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/weakref.pyi b/mypy/typeshed/stdlib/weakref.pyi index 1e0aac814dfb3..13f48fe85a8d4 100644 --- a/mypy/typeshed/stdlib/weakref.pyi +++ b/mypy/typeshed/stdlib/weakref.pyi @@ -41,7 +41,7 @@ _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: ... + 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: ... @@ -53,7 +53,7 @@ class WeakValueDictionary(MutableMapping[_KT, _VT]): def __init__(self: WeakValueDictionary[_KT, _VT], __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], __other: Mapping[str, _VT] | Iterable[tuple[str, _VT]] = (), **kwargs: _VT ) -> None: ... def __len__(self) -> int: ... def __getitem__(self, key: _KT) -> _VT: ... diff --git a/mypy/typeshed/stdlib/webbrowser.pyi b/mypy/typeshed/stdlib/webbrowser.pyi index 02edd42e7d598..99c7bb5846b62 100644 --- a/mypy/typeshed/stdlib/webbrowser.pyi +++ b/mypy/typeshed/stdlib/webbrowser.pyi @@ -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 5b2d09a3bebcb..70ea6a1ced115 100644 --- a/mypy/typeshed/stdlib/winreg.pyi +++ b/mypy/typeshed/stdlib/winreg.pyi @@ -14,7 +14,7 @@ if sys.platform == "win32": 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 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: ... diff --git a/mypy/typeshed/stdlib/xml/dom/domreg.pyi b/mypy/typeshed/stdlib/xml/dom/domreg.pyi index a46d3ff090e6a..346a4bf63bd4d 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 4507b3d98ee76..4d83bef025d94 100644 --- a/mypy/typeshed/stdlib/xml/dom/minicompat.pyi +++ b/mypy/typeshed/stdlib/xml/dom/minicompat.pyi @@ -1,5 +1,6 @@ from collections.abc import Iterable from typing import Any, TypeVar +from typing_extensions import Literal __all__ = ["NodeList", "EmptyNodeList", "StringTypes", "defproperty"] @@ -8,11 +9,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 7bbffb88c8f73..ec17f0a41497a 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 import NoReturn, TypeVar, overload from typing_extensions import Literal, 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 @@ -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/etree/ElementTree.pyi b/mypy/typeshed/stdlib/xml/etree/ElementTree.pyi index db33b2d673d7e..2cf8dbbe70258 100644 --- a/mypy/typeshed/stdlib/xml/etree/ElementTree.pyi +++ b/mypy/typeshed/stdlib/xml/etree/ElementTree.pyi @@ -58,13 +58,13 @@ if sys.version_info >= (3, 8): *, 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 = ..., + 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( @@ -72,13 +72,13 @@ if sys.version_info >= (3, 8): *, 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 = ..., + 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: @@ -112,18 +112,18 @@ class Element: 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 __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: ... if sys.version_info < (3, 9): def getchildren(self) -> list[Element]: ... def getiterator(self, tag: str | None = None) -> list[Element]: ... diff --git a/mypy/typeshed/stdlib/xml/sax/__init__.pyi b/mypy/typeshed/stdlib/xml/sax/__init__.pyi index ca981a00d25fa..8bcf902df8d8e 100644 --- a/mypy/typeshed/stdlib/xml/sax/__init__.pyi +++ b/mypy/typeshed/stdlib/xml/sax/__init__.pyi @@ -28,7 +28,7 @@ class SAXReaderNotAvailable(SAXNotSupportedException): ... default_parser_list: list[str] if sys.version_info >= (3, 8): - def make_parser(parser_list: Iterable[str] = ...) -> XMLReader: ... + def make_parser(parser_list: Iterable[str] = ()) -> XMLReader: ... def parse( source: StrPath | _SupportsReadClose[bytes] | _SupportsReadClose[str], handler: ContentHandler, @@ -36,7 +36,7 @@ if sys.version_info >= (3, 8): ) -> None: ... else: - def make_parser(parser_list: list[str] = ...) -> XMLReader: ... + def make_parser(parser_list: list[str] = []) -> XMLReader: ... def parse( source: str | _SupportsReadClose[bytes] | _SupportsReadClose[str], handler: ContentHandler, diff --git a/mypy/typeshed/stdlib/xml/sax/saxutils.pyi b/mypy/typeshed/stdlib/xml/sax/saxutils.pyi index 67a06d2fcda22..0d9223770c6a7 100644 --- a/mypy/typeshed/stdlib/xml/sax/saxutils.pyi +++ b/mypy/typeshed/stdlib/xml/sax/saxutils.pyi @@ -4,9 +4,9 @@ from collections.abc import Mapping from io import RawIOBase, TextIOBase from xml.sax import 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__( diff --git a/mypy/typeshed/stdlib/xmlrpc/client.pyi b/mypy/typeshed/stdlib/xmlrpc/client.pyi index 7bf701ae716d5..79969359f0910 100644 --- a/mypy/typeshed/stdlib/xmlrpc/client.pyi +++ b/mypy/typeshed/stdlib/xmlrpc/client.pyi @@ -2,7 +2,7 @@ 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 @@ -230,26 +230,26 @@ class Transport: if sys.version_info >= (3, 8): def __init__( - self, use_datetime: bool = False, use_builtin_types: bool = False, *, headers: Iterable[tuple[str, str]] = ... + 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 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): @@ -259,7 +259,7 @@ class SafeTransport(Transport): use_datetime: bool = False, use_builtin_types: bool = False, *, - headers: Iterable[tuple[str, str]] = ..., + headers: Iterable[tuple[str, str]] = (), context: Any | None = None, ) -> None: ... else: @@ -288,7 +288,7 @@ class ServerProxy: use_datetime: bool = False, use_builtin_types: bool = False, *, - headers: Iterable[tuple[str, str]] = ..., + headers: Iterable[tuple[str, str]] = (), context: Any | None = None, ) -> None: ... else: diff --git a/mypy/typeshed/stdlib/xmlrpc/server.pyi b/mypy/typeshed/stdlib/xmlrpc/server.pyi index 800c205513c65..832fe265e0a59 100644 --- a/mypy/typeshed/stdlib/xmlrpc/server.pyi +++ b/mypy/typeshed/stdlib/xmlrpc/server.pyi @@ -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/zipfile.pyi b/mypy/typeshed/stdlib/zipfile.pyi index b969d0cf9e6a3..abda7a3b96257 100644 --- a/mypy/typeshed/stdlib/zipfile.pyi +++ b/mypy/typeshed/stdlib/zipfile.pyi @@ -1,6 +1,6 @@ import io import sys -from _typeshed import StrOrBytesPath, StrPath, _BufferWithLen +from _typeshed import SizedBuffer, StrOrBytesPath, StrPath from collections.abc import Callable, Iterable, Iterator from os import PathLike from types import TracebackType @@ -179,7 +179,7 @@ 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: ... @@ -211,7 +211,7 @@ class ZipInfo: compress_size: int file_size: int orig_filename: str # undocumented - def __init__(self, filename: str = "NoName", date_time: _DateTuple = ...) -> None: ... + def __init__(self, filename: str = "NoName", date_time: _DateTuple = (1980, 1, 1, 0, 0, 0)) -> None: ... if sys.version_info >= (3, 8): @classmethod def from_file(cls, filename: StrPath, arcname: StrPath | None = None, *, strict_timestamps: bool = True) -> Self: ... @@ -222,10 +222,11 @@ class ZipInfo: 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, 8): + if sys.version_info < (3, 9): + class _PathOpenProtocol(Protocol): + def __call__(self, mode: _ReadWriteMode = "r", pwd: bytes | None = ..., *, force_zip64: bool = ...) -> IO[bytes]: ... + class Path: @property def name(self) -> str: ... @@ -245,7 +246,12 @@ if sys.version_info >= (3, 8): def __init__(self, root: ZipFile | StrPath | IO[bytes], at: str = "") -> None: ... if sys.version_info >= (3, 9): def open( - self, mode: _ReadWriteBinaryMode = "r", *args: Any, pwd: bytes | None = None, **kwargs: Any + self, + mode: _ReadWriteBinaryMode = "r", + encoding: str | None = None, + *args: Any, + pwd: bytes | None = None, + **kwargs: Any, ) -> IO[bytes]: ... else: @property diff --git a/mypy/typeshed/stdlib/zipimport.pyi b/mypy/typeshed/stdlib/zipimport.pyi index ee97faace379e..0189bfe712b5a 100644 --- a/mypy/typeshed/stdlib/zipimport.pyi +++ b/mypy/typeshed/stdlib/zipimport.pyi @@ -17,8 +17,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/zoneinfo/__init__.pyi b/mypy/typeshed/stdlib/zoneinfo/__init__.pyi index fe994be3e8ff4..a95530ed461ab 100644 --- a/mypy/typeshed/stdlib/zoneinfo/__init__.pyi +++ b/mypy/typeshed/stdlib/zoneinfo/__init__.pyi @@ -17,9 +17,9 @@ 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 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: ... @@ -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 40e24645fb772..b6358a0022f3b 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 9cbad17aa7bdf..b32fb0ef6df14 100644 --- a/mypy/typestate.py +++ b/mypy/typestate.py @@ -5,13 +5,16 @@ 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.server.trigger import make_trigger from mypy.types import Instance, Type, TypeVarId, 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: @@ -164,6 +195,21 @@ def record_subtype_cache_entry( cache = self._subtype_caches.setdefault(right.type, dict()) 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, dict()) + 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: """Reset dependencies after a full run or before a daemon shutdown.""" self.proto_deps = {} @@ -269,7 +315,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/typevars.py b/mypy/typevars.py index 69c2eed37fa4f..027a8e3f7fc57 100644 --- a/mypy/typevars.py +++ b/mypy/typevars.py @@ -27,16 +27,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 +36,7 @@ def fill_typevars(typ: TypeInfo) -> Instance | TupleType: tv.id, tv.upper_bound, tv.tuple_fallback, + tv.default, line=-1, column=-1, ) @@ -52,7 +44,14 @@ 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) diff --git a/mypy/typevartuples.py b/mypy/typevartuples.py index 29b85dae72eb4..ac5f4e43c3bf3 100644 --- a/mypy/typevartuples.py +++ b/mypy/typevartuples.py @@ -2,17 +2,24 @@ 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 +from mypy.types import ( + CallableType, + Instance, + ProperType, + Type, + UnpackType, + get_proper_type, + split_with_prefix_and_suffix, +) 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): + 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. @@ -23,18 +30,6 @@ def find_unpack_in_list(items: Sequence[Type]) -> int | None: 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:], ()) - - def split_with_instance( typ: Instance, ) -> tuple[tuple[Type, ...], tuple[Type, ...], tuple[Type, ...]]: @@ -52,14 +47,17 @@ def split_with_mapped_and_template( 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: +) -> ( + 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, @@ -101,18 +99,21 @@ def fully_split_with_mapped_and_template( 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: +) -> ( + 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( @@ -175,9 +176,8 @@ def fully_split_with_mapped_and_template( 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 diff --git a/mypy/util.py b/mypy/util.py index 2c225c7fe6510..268ba8f9de813 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 @@ -421,10 +421,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): 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) ) diff --git a/mypy/version.py b/mypy/version.py index 258a0e4f8bcbe..a394ce2549790 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.5.1" base_version = __version__ mypy_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) diff --git a/mypy_self_check.ini b/mypy_self_check.ini index d20fcd60a9cb4..7413e6407d4fd 100644 --- a/mypy_self_check.ini +++ b/mypy_self_check.ini @@ -6,9 +6,10 @@ show_traceback = True pretty = True always_false = MYPYC plugins = misc/proper_plugin.py -python_version = 3.7 +python_version = 3.8 exclude = mypy/typeshed/|mypyc/test-data/|mypyc/lib-rt/ enable_error_code = ignore-without-code,redundant-expr +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 0000000000000..90831dfd70694 --- /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 02e02a82a4f99..350158246cdb1 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 21c4da8981d10..ee2ff06b0f031 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, @@ -85,7 +89,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 +145,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]: @@ -245,9 +248,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) @@ -444,7 +456,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 +466,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 719faebfcee8c..2e6b7320e8987 100644 --- a/mypyc/analysis/ircheck.py +++ b/mypyc/analysis/ircheck.py @@ -16,6 +16,9 @@ ControlOp, DecRef, Extend, + FloatComparisonOp, + FloatNeg, + FloatOp, GetAttr, GetElementPtr, Goto, @@ -43,6 +46,7 @@ TupleSet, Unbox, Unreachable, + Value, ) from mypyc.ir.pprint import format_func from mypyc.ir.rtypes import ( @@ -54,6 +58,7 @@ bytes_rprimitive, dict_rprimitive, int_rprimitive, + is_float_rprimitive, is_object_rprimitive, list_rprimitive, range_rprimitive, @@ -221,6 +226,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) @@ -376,10 +389,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 diff --git a/mypyc/analysis/selfleaks.py b/mypyc/analysis/selfleaks.py index 16c1050acf917..288c366e50e5f 100644 --- a/mypyc/analysis/selfleaks.py +++ b/mypyc/analysis/selfleaks.py @@ -14,6 +14,9 @@ Cast, ComparisonOp, Extend, + FloatComparisonOp, + FloatNeg, + FloatOp, GetAttr, GetElementPtr, Goto, @@ -160,6 +163,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 diff --git a/mypyc/build.py b/mypyc/build.py index cc03eba95b4ee..9889577d4addd 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,49 @@ from mypyc.namegen import exported_name from mypyc.options import CompilerOptions -if TYPE_CHECKING: - from distutils.core import Extension +if sys.version_info < (3, 12): + if TYPE_CHECKING: + from distutils.core import Extension as _distutils_Extension + from typing_extensions import TypeAlias -try: - # Import setuptools so that it monkey-patch overrides distutils - import setuptools # noqa: F401 -except ImportError: - 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 as _setuptools_Extension + + Extension: TypeAlias = Union[_setuptools_Extension, _distutils_Extension] + + try: + # Import setuptools so that it monkey-patch overrides distutils + import setuptools + except ImportError: + pass + from distutils import ccompiler, sysconfig +else: + import setuptools + from setuptools import Extension + from setuptools._distutils import ( + ccompiler as _ccompiler, # type: ignore[attr-defined] + sysconfig as _sysconfig, # type: ignore[attr-defined] + ) + + ccompiler = _ccompiler + sysconfig = _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 sys.version_info < (3, 12) and not use_setuptools: + import distutils.core - if not use_setuptools: - from distutils.core import Extension + 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: @@ -216,7 +235,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 e006f12e09ecf..853787f8161d4 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 6e0c89dd0ecf3..7d41ee7e162b6 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 @@ -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 a9b51b8ff1a4d..84d19d69d3778 100644 --- a/mypyc/codegen/emitclass.py +++ b/mypyc/codegen/emitclass.py @@ -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( @@ -1004,6 +1016,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 +1029,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 +1047,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 e7fb7db80413d..b4d31544b1967 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, @@ -119,6 +124,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 +197,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 +232,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 +241,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) @@ -258,12 +281,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 +301,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 +484,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) @@ -632,12 +647,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 +681,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,7 +733,13 @@ 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: @@ -732,6 +769,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 +790,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 9f65aa77c47f4..f360fabbe8f66 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 ( @@ -51,7 +51,7 @@ 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 @@ -296,11 +296,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 +369,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() @@ -465,7 +465,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 +512,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 +528,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 +582,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 +790,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);", @@ -1023,12 +1023,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 +1052,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: @@ -1126,37 +1123,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 ed03bb7948ccb..791e856c274ac 100644 --- a/mypyc/codegen/emitwrapper.py +++ b/mypyc/codegen/emitwrapper.py @@ -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: diff --git a/mypyc/codegen/literals.py b/mypyc/codegen/literals.py index 05884b754452f..1f0c3bc6ec7bd 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 c8da5ff63bab1..4615bf30d7427 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", diff --git a/mypyc/doc/differences_from_python.rst b/mypyc/doc/differences_from_python.rst index 16faae60303fc..f1d4d05a3a871 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 c1e4d284c4ba8..feae5a806c70b 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 8d3bf5bba662e..2db8aae149ec7 100644 --- a/mypyc/doc/getting_started.rst +++ b/mypyc/doc/getting_started.rst @@ -32,7 +32,7 @@ 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 ------------ diff --git a/mypyc/doc/int_operations.rst b/mypyc/doc/int_operations.rst index 038b6e5dbc633..eb875f5c94523 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 874071bac23fd..53c86ecdab1b5 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 2119f51099bf3..153be5e2f6f99 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 2b4a0892b7905..b2935a6f71856 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 668d328274021..ae0b2950814c7 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 a01246ab09141..04c923819d547 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 1dd269fe25f31..8bc9b2714f75f 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 a1534780b79ba..682e306291189 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 @@ -73,7 +73,7 @@ class VTableMethod(NamedTuple): cls: "ClassIR" 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. diff --git a/mypyc/ir/func_ir.py b/mypyc/ir/func_ir.py index dbb45fc7ec294..44847c7bb0b3b 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 4b6a177af149e..dcf6f87685474 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 51a0bffcf3f17..d80c479211b7b 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,7 +570,7 @@ 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) @@ -770,7 +790,7 @@ 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 accept(self, visitor: OpVisitor[T]) -> T: return visitor.visit_tuple_set(self) @@ -895,6 +915,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) @@ -1042,7 +1063,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 +1162,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 +1178,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 +1349,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 @@ -1285,7 +1396,7 @@ def __init__(self, src: list[Value]) -> None: self.src = src def sources(self) -> list[Value]: - return self.src[:] + return self.src.copy() def accept(self, visitor: OpVisitor[T]) -> T: return visitor.visit_keep_alive(self) @@ -1405,6 +1516,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 @@ -1428,6 +1551,7 @@ def visit_keep_alive(self, op: KeepAlive) -> T: # 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 +1575,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 cb9e4a2d25418..c86060c495945 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, @@ -241,6 +244,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,6 +265,11 @@ 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) @@ -289,6 +306,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 +464,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 babfe0770f35e..fa46feb0b59ad 100644 --- a/mypyc/ir/rtypes.py +++ b/mypyc/ir/rtypes.py @@ -24,7 +24,7 @@ from abc import abstractmethod from typing import TYPE_CHECKING, ClassVar, Generic, TypeVar -from typing_extensions import Final +from typing_extensions import Final, 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/builder.py b/mypyc/irbuild/builder.py index f37fae608083b..8c68f91bf6339 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -13,8 +13,8 @@ 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,16 +41,19 @@ Statement, SymbolNode, TupleExpr, + TypeAlias, TypeInfo, UnaryExpr, Var, ) from mypy.types import ( AnyType, + DeletedType, Instance, ProperType, TupleType, Type, + TypedDictType, TypeOfAny, UninhabitedType, UnionType, @@ -88,13 +91,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 +127,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,7 +159,7 @@ def __init__( options: CompilerOptions, singledispatch_impls: dict[FuncDef, list[RegisterImplInfo]], ) -> None: - self.builder = LowLevelIRBuilder(current_module, mapper, options) + self.builder = LowLevelIRBuilder(current_module, errors, mapper, options) self.builders = [self.builder] self.symtables: list[dict[SymbolNode, SymbolTarget]] = [{}] self.runtime_args: list[list[RuntimeArg]] = [[]] @@ -190,6 +189,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,6 +224,7 @@ 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: @@ -301,6 +303,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) @@ -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 @@ -547,16 +536,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 +551,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 +571,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 +586,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 +654,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 +687,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 +742,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 +899,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,7 +1014,7 @@ 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) + return self.load_literal_value(final_var.final_value) elif native: return self.load_final_static(fullname, self.mapper.type_to_rtype(typ), line, name) else: @@ -1015,10 +1026,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 +1103,10 @@ 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.current_module, self.errors, self.mapper, self.options + ) + self.builder.set_module(self.module_name, self.module_path) self.builders.append(self.builder) self.symtables.append({}) self.runtime_args.append([]) @@ -1349,3 +1363,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 d3ee54a208cd8..599dbb81f7678 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 59b1c05a0ddbd..fc2bb4a1fc2f3 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, @@ -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" diff --git a/mypyc/irbuild/constant_fold.py b/mypyc/irbuild/constant_fold.py index 4e9eb53b9222f..12a4b15dd40c8 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/expression.py b/mypyc/irbuild/expression.py index 3f5b795a14363..8d205b432d2d5 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, @@ -89,7 +91,6 @@ 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 @@ -126,6 +127,10 @@ def transform_name_expr(builder: IRBuilder, expr: NameExpr) -> Value: if fullname == "builtins.False": 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( expr.node, @@ -174,7 +179,7 @@ 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) @@ -190,6 +195,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 +297,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 +348,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 +357,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 +389,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 +459,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 +503,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 +573,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 +656,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 +675,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 +708,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]) @@ -789,26 +810,35 @@ def transform_basic_comparison( if ( is_int_rprimitive(left.type) and is_int_rprimitive(right.type) - and op in int_comparison_op_mapping.keys() + and op in int_comparison_op_mapping ): 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 int_comparison_op_mapping: 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 int_comparison_op_mapping 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 @@ -1020,3 +1050,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/format_str_tokenizer.py b/mypyc/irbuild/format_str_tokenizer.py index 5ab38d0f2264c..0b46887811fb5 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 02155d70e928e..822350ea829b5 100644 --- a/mypyc/irbuild/function.py +++ b/mypyc/irbuild/function.py @@ -435,7 +435,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 +515,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 +642,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 diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 2391ccc4d0ed4..984b6a4deec0f 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, @@ -64,9 +68,12 @@ 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 ( @@ -88,6 +95,7 @@ c_pointer_rprimitive, c_pyssize_t_rprimitive, c_size_t_rprimitive, + check_native_int_range, dict_rprimitive, float_rprimitive, int_rprimitive, @@ -96,6 +104,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 +116,7 @@ is_str_rprimitive, is_tagged, is_tuple_rprimitive, + is_uint8_rprimitive, list_rprimitive, none_rprimitive, object_pointer_rprimitive, @@ -126,6 +137,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,6 +149,9 @@ 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, @@ -147,6 +162,7 @@ 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, new_list_op from mypyc.primitives.misc_ops import bool_op, fast_isinstance_op, none_object_op @@ -204,8 +220,11 @@ class LowLevelIRBuilder: - def __init__(self, current_module: str, mapper: Mapper, options: CompilerOptions) -> None: + def __init__( + self, current_module: str, errors: Errors, mapper: Mapper, options: CompilerOptions + ) -> None: self.current_module = current_module + self.errors = errors self.mapper = mapper self.options = options self.args: list[Register] = [] @@ -216,6 +235,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: @@ -257,7 +281,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 +335,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 +366,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 +427,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 +479,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 +500,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 +911,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 +986,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 +1094,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 +1235,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. @@ -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,7 +1375,7 @@ 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) @@ -1328,6 +1395,24 @@ def binary_op(self, lreg: Value, rreg: Value, op: str, line: int) -> Value: 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) + 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) call_c_ops_candidates = binary_ops.get(op, []) target = self.matching_call_c(call_c_ops_candidates, [lreg, rreg], line) @@ -1552,10 +1637,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 +1660,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,7 +1729,7 @@ 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)) @@ -1651,6 +1749,16 @@ def new_list_op(self, values: list[Value], line: int) -> Value: 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 +1821,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: @@ -1890,7 +2000,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 +2070,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 +2148,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 +2161,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)) @@ -2042,6 +2242,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 +2390,9 @@ 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: + 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 9bbb90aad2076..85b905393af1a 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 dddb35230fd5c..5b77b4b1537b2 100644 --- a/mypyc/irbuild/mapper.py +++ b/mypyc/irbuild/mapper.py @@ -32,6 +32,7 @@ bytes_rprimitive, dict_rprimitive, float_rprimitive, + int16_rprimitive, int32_rprimitive, int64_rprimitive, int_rprimitive, @@ -42,6 +43,7 @@ set_rprimitive, str_rprimitive, tuple_rprimitive, + uint8_rprimitive, ) @@ -102,6 +104,10 @@ 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): diff --git a/mypyc/irbuild/prebuildvisitor.py b/mypyc/irbuild/prebuildvisitor.py index d994539550021..519b3445e925a 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 @@ -123,6 +143,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 b3d10887ce216..5e65200481971 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 @@ -465,7 +465,6 @@ 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 diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 8cb24c5b47dac..7c59584578866 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 b9754ba1a147d..63297618108c7 100644 --- a/mypyc/irbuild/statement.py +++ b/mypyc/irbuild/statement.py @@ -43,13 +43,17 @@ YieldFromExpr, ) from mypyc.ir.ops import ( + NAMESPACE_MODULE, NO_TRACEBACK_LINE_NO, Assign, BasicBlock, Branch, + InitStatic, Integer, LoadAddress, LoadErrorValue, + LoadLiteral, + LoadStatic, MethodCall, RaiseStandardError, Register, @@ -60,6 +64,7 @@ ) from mypyc.ir.rtypes import ( RInstance, + c_pyssize_t_rprimitive, exc_rtuple, is_tagged, none_rprimitive, @@ -96,7 +101,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, @@ -145,7 +151,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 +175,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() @@ -214,35 +220,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 +322,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/lib-rt/CPy.h b/mypyc/lib-rt/CPy.h index 016a6d3ea9e06..64b716945b944 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 @@ -604,8 +625,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 bece7c13c9574..4da62be115718 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 219914bf34702..d8307ecf21f85 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 0000000000000..d8c6f25955fa7 --- /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 5ea2f65d5776e..b57d88c6ac934 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 5fda78704bbc1..f28eeb57e6467 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 0fae239cbb9ec..3c888a581a33f 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 8a1159a988538..1d493b45b89d8 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) @@ -389,6 +454,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 +492,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 e04d7041ad722..a31b705cd723b 100644 --- a/mypyc/lib-rt/setup.py +++ b/mypyc/lib-rt/setup.py @@ -7,12 +7,14 @@ import sys 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"] setup( @@ -21,7 +23,15 @@ 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"], diff --git a/mypyc/primitives/dict_ops.py b/mypyc/primitives/dict_ops.py index 9f477d0b7b90c..ce7b9bb8d70e1 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 535606df6176a..14e8d4caf09cf 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 7eda9bab7e3c6..95f9cc5ff43f1 100644 --- a/mypyc/primitives/int_ops.py +++ b/mypyc/primitives/int_ops.py @@ -19,6 +19,7 @@ bool_rprimitive, c_pyssize_t_rprimitive, float_rprimitive, + int16_rprimitive, int32_rprimitive, int64_rprimitive, int_rprimitive, @@ -37,7 +38,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 +57,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, ) @@ -126,6 +133,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,6 +168,7 @@ def int_unary_op(name: str, c_function_name: str) -> CFunctionDescription: # Primitives related to integer comparison operations: + # Description for building int comparison ops # # Fields: @@ -226,6 +238,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 +288,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/misc_ops.py b/mypyc/primitives/misc_ops.py index 07df9c69714ba..5a8cc111ebc25 100644 --- a/mypyc/primitives/misc_ops.py +++ b/mypyc/primitives/misc_ops.py @@ -7,10 +7,10 @@ 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, str_rprimitive, @@ -112,7 +112,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 +120,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, ) diff --git a/mypyc/primitives/registry.py b/mypyc/primitives/registry.py index 1e2cf2695ee7c..aa96b35aec562 100644 --- a/mypyc/primitives/registry.py +++ b/mypyc/primitives/registry.py @@ -37,8 +37,7 @@ 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.rtypes import RType @@ -50,16 +49,16 @@ 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 diff --git a/mypyc/rt_subtype.py b/mypyc/rt_subtype.py index f3fe1a442d220..004e56ed75bc9 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/driver/driver.py b/mypyc/test-data/driver/driver.py index 6717f402f72d2..c9d179224a30d 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 187551249676e..ed43b86ebdb44 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 @@ -528,10 +528,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 +542,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 +570,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 +612,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 +636,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 +665,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 27e225f273bc1..bf06613ad2a89 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 7b4fcc9fc1ca3..7f00ee5aea001 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 c36b1001106ef..52bca09a1decc 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 8d4e085179ae8..98f3dae9ee881 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,10 +99,10 @@ 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 @@ -187,15 +187,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 +227,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 a06977d037b20..33fc8cfaa83b7 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -682,14 +682,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 +789,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 +804,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 @@ -1001,7 +1146,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 +1161,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 @@ -1176,10 +1313,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 +1331,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 +1425,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 @@ -1398,7 +1533,7 @@ def opt_o(x): r0 :: object r1 :: bit r2 :: object - r3 :: int32 + r3 :: i32 r4 :: bit r5 :: bool L0: @@ -1409,7 +1544,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 +1616,7 @@ def __top_level__(): r5 :: dict r6 :: str r7 :: object - r8 :: int32 + r8 :: i32 r9 :: bit r10 :: dict r11 :: str @@ -1596,7 +1731,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 +1740,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() @@ -1768,7 +1903,7 @@ def g(): r8 :: str r9 :: object r10 :: dict - r11 :: int32 + r11 :: i32 r12 :: bit r13 :: tuple r14 :: object @@ -1798,7 +1933,7 @@ def h(): r6 :: str r7 :: object r8 :: dict - r9 :: int32 + r9 :: i32 r10 :: bit r11 :: object r12 :: tuple @@ -1927,7 +2062,7 @@ def f(): r26, r27 :: bit r28 :: int r29 :: object - r30 :: int32 + r30 :: i32 r31 :: bit r32 :: short_int L0: @@ -2026,7 +2161,7 @@ def f(): r26, r27 :: bit r28 :: int r29, r30 :: object - r31 :: int32 + r31 :: i32 r32 :: bit r33 :: short_int L0: @@ -2276,79 +2411,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, r65, r66, r67 :: ptr + r68 :: dict + r69 :: str + r70 :: i32 + r71 :: bit L0: r0 = builtins :: module r1 = load_address _Py_NoneStruct @@ -2359,110 +2476,78 @@ 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 = get_element_ptr r60 ob_item :: PyListObject + r65 = load_mem r64 :: ptr* + set_mem r65, r61 :: builtins.object* + r66 = r65 + WORD_SIZE*1 + set_mem r66, r62 :: builtins.object* + r67 = r65 + WORD_SIZE*2 + set_mem r67, r63 :: builtins.object* + keep_alive r60 + r68 = __main__.globals :: static + r69 = 'y' + r70 = CPyDict_SetItem(r68, r69, r60) + r71 = r70 >= 0 :: signed return 1 [case testChainedConditional] @@ -2547,7 +2632,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 +2644,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: @@ -2751,7 +2836,7 @@ def c(): r11 :: bool r12 :: dict r13 :: str - r14 :: int32 + r14 :: i32 r15 :: bit r16 :: str r17 :: object @@ -2786,31 +2871,23 @@ 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 +2898,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] @@ -2927,18 +2992,10 @@ 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 +3006,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] @@ -3112,7 +3157,7 @@ def lol(x: Any): def lol(x): x :: object r0, r1 :: str - r2 :: int32 + r2 :: i32 r3 :: bit r4 :: object L0: @@ -3417,34 +3462,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 +3569,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 +3589,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] @@ -3573,7 +3679,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 +3688,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] diff --git a/mypyc/test-data/irbuild-bool.test b/mypyc/test-data/irbuild-bool.test index 9257d8d63f7eb..731d393d69ab1 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] @@ -100,13 +100,13 @@ L0: return r3 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 @@ -236,20 +236,20 @@ L0: r2 = 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 @@ -327,19 +327,19 @@ L3: return r8 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 +386,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 f13a1a956580e..8e97a7f4a5690 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) diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index b9501c32180df..55e55dbf3286c 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -200,84 +200,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 +267,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 +601,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 +837,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 +849,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 +957,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 +969,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 +1029,7 @@ def foo(x: WelpDict) -> None: [out] def foo(x): x :: dict - r0 :: int32 + r0 :: i32 r1 :: bit L0: r0 = CPyDict_Update(x, x) diff --git a/mypyc/test-data/irbuild-constant-fold.test b/mypyc/test-data/irbuild-constant-fold.test index 7d9127887aa6b..97b13ab337c73 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 99643b9451f0b..1a84f3fe3098b 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: @@ -514,4 +583,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 82f04dcdf6870..b50b6eeae1628 100644 --- a/mypyc/test-data/irbuild-dunders.test +++ b/mypyc/test-data/irbuild-dunders.test @@ -103,14 +103,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 +184,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 +221,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 0000000000000..35e2eff62b863 --- /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 fe4a949927174..35920889e5962 100644 --- a/mypyc/test-data/irbuild-generics.test +++ b/mypyc/test-data/irbuild-generics.test @@ -132,7 +132,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 +140,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 6d749bf5dd848..3012c79586f25 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 0000000000000..a03c9df2c6ac4 --- /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 7ea3c08647285..7dcb722ec9067 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 f616893d8fe53..07f549c9fcc20 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,7 +1137,7 @@ class C: s: str [out] def f(n): - n :: int64 + n :: i64 r0 :: __main__.C r1 :: list r2, r3 :: ptr @@ -1173,13 +1170,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,11 +1201,11 @@ def g(a: List[i64], y: i64) -> bool: [out] def f(a, y): a :: list - y :: int64 + y :: i64 r0 :: ptr r1 :: native_int r2 :: short_int - r3 :: int64 + r3 :: i64 r4 :: bit L0: r0 = get_element_ptr a ob_size :: PyVarObject @@ -1224,11 +1221,11 @@ L2: return 0 def g(a, y): a :: list - y :: int64 + y :: i64 r0 :: ptr r1 :: native_int r2 :: short_int - r3 :: int64 + r3 :: i64 r4 :: bit L0: r0 = get_element_ptr a ob_size :: PyVarObject @@ -1251,7 +1248,7 @@ def f(n: i64) -> List[i64]: return [n] * n [out] def f(n): - n :: int64 + n :: i64 r0 :: list r1 :: object r2, r3 :: ptr @@ -1260,7 +1257,7 @@ def f(n): r9 :: list L0: r0 = PyList_New(1) - r1 = box(int64, n) + r1 = box(i64, n) r2 = get_element_ptr r0 ob_item :: PyListObject r3 = load_mem r2 :: ptr* set_mem r3, r1 :: builtins.object* @@ -1300,11 +1297,11 @@ def lt_i64(a: List[i64], n: i64) -> bool: [out] def add_i64(a, n): a :: list - n :: int64 + n :: i64 r0 :: ptr r1 :: native_int r2 :: short_int - r3, r4 :: int64 + r3, r4 :: i64 L0: r0 = get_element_ptr a ob_size :: PyVarObject r1 = load_mem r0 :: native_int* @@ -1315,11 +1312,11 @@ L0: return r4 def add_i64_2(a, n): a :: list - n :: int64 + n :: i64 r0 :: ptr r1 :: native_int r2 :: short_int - r3, r4 :: int64 + r3, r4 :: i64 L0: r0 = get_element_ptr a ob_size :: PyVarObject r1 = load_mem r0 :: native_int* @@ -1330,11 +1327,11 @@ L0: return r4 def eq_i64(a, n): a :: list - n :: int64 + n :: i64 r0 :: ptr r1 :: native_int r2 :: short_int - r3 :: int64 + r3 :: i64 r4 :: bit L0: r0 = get_element_ptr a ob_size :: PyVarObject @@ -1350,11 +1347,11 @@ L2: return 0 def lt_i64(a, n): a :: list - n :: int64 + n :: i64 r0 :: ptr r1 :: native_int r2 :: short_int - r3 :: int64 + r3 :: i64 r4 :: bit L0: r0 = get_element_ptr a ob_size :: PyVarObject @@ -1379,10 +1376,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 +1387,7 @@ L0: L1: return 1 L2: - r2 = unbox(int64, x) + r2 = unbox(i64, x) return r2 [case testI64DefaultValueSingle] @@ -1403,10 +1400,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 +1414,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 +1431,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 +1458,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 +1486,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 +1506,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 +1517,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 +1544,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 +1572,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 +1600,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 +1615,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 +1642,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 +1671,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 +1684,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 +1751,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 +1788,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 +1828,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 +1850,7 @@ L2: L3: return r4 -[case testI64ExplicitConversionFromVariousTypes] +[case testI64ExplicitConversionFromVariousTypes_64bit] from mypy_extensions import i64 def bool_to_i64(b: bool) -> i64: @@ -1868,25 +1874,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 +1900,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 +2042,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 +2065,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-isinstance.test b/mypyc/test-data/irbuild-isinstance.test index 6bb92d0a947e8..78da2e9c1e196 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 cb9687a2f942b..80c4fe5fcd5e0 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: @@ -181,7 +197,7 @@ def f(a, x): a :: list x :: int r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit L0: r0 = box(int, x) @@ -239,7 +255,7 @@ def f(x, y): r1, r2 :: object r3, r4, r5 :: ptr r6, r7, r8 :: object - r9 :: int32 + r9 :: i32 r10 :: bit L0: r0 = PyList_New(2) @@ -267,14 +283,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 +302,7 @@ def f(x, y): x :: list y :: int r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit L0: r0 = box(int, y) @@ -446,7 +462,7 @@ 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 @@ -458,7 +474,7 @@ 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) diff --git a/mypyc/test-data/irbuild-match.test b/mypyc/test-data/irbuild-match.test index 2afe3d862f517..a078ae0defdbf 100644 --- a/mypyc/test-data/irbuild-match.test +++ b/mypyc/test-data/irbuild-match.test @@ -608,22 +608,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 +637,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 +646,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 +655,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 +664,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 +693,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 +722,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 +731,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 +740,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 +749,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 +776,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 +799,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 +808,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 +856,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 +881,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 +893,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 +902,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 +940,7 @@ L0: return 1 def f(x): x, r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool r4 :: str @@ -957,7 +957,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 +986,7 @@ def f(x): [out] def f(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: str r3 :: object @@ -1021,15 +1021,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 +1054,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 +1078,7 @@ def f(x): [out] def f(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2, rest :: dict r3 :: str @@ -1117,19 +1117,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 +1153,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 +1182,7 @@ def f(x): [out] def f(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: native_int r3, r4 :: bit @@ -1224,16 +1224,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 +1258,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 +1266,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 +1290,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 +1324,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 +1332,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 +1356,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 +1392,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 +1400,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 +1428,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 +1469,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 +1478,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 +1506,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 +1545,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 +1554,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 +1620,7 @@ def f(x): [out] def f(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: native_int r3, r4 :: bit @@ -1674,7 +1674,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 +1687,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 0000000000000..470e60c74f7df --- /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 c5de7d6c02d09..adef80263533a 100644 --- a/mypyc/test-data/irbuild-nested.test +++ b/mypyc/test-data/irbuild-nested.test @@ -859,4 +859,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 e98cf1b19e2e9..e89018a727dab 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 @@ -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 c567422abac76..a56ebe3438fa0 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) @@ -90,7 +90,7 @@ def test1(): r14 :: object r15, x, r16 :: int r17 :: object - r18 :: int32 + r18 :: i32 r19 :: bit r20 :: short_int a :: set @@ -138,7 +138,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 +179,7 @@ def test3(): r15 :: object r16, x, r17 :: int r18 :: object - r19 :: int32 + r19 :: i32 r20, r21, r22 :: bit c :: set L0: @@ -225,7 +225,7 @@ def test4(): r2 :: bit r3 :: int r4 :: object - r5 :: int32 + r5 :: i32 r6 :: bit r7 :: short_int d :: set @@ -256,7 +256,7 @@ def test5(): r2 :: bit r3 :: int r4 :: object - r5 :: int32 + r5 :: i32 r6 :: bit r7 :: short_int e :: set @@ -331,18 +331,18 @@ def test(): r29 :: bit r30 :: int r31 :: object - r32 :: int32 + r32 :: i32 r33 :: bit r34 :: short_int r35, r36, r37 :: object r38, y, r39 :: int r40 :: object - r41 :: int32 + r41 :: i32 r42, r43 :: bit r44, r45, r46 :: object r47, x, r48 :: int r49 :: object - r50 :: int32 + r50 :: i32 r51, r52 :: bit a :: set L0: @@ -452,13 +452,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 +489,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 +511,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 +542,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 +562,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 +581,7 @@ def f() -> Set[int]: [out] def f(): r0, x :: set - r1 :: int32 + r1 :: i32 r2 :: bit L0: r0 = PySet_New(0) @@ -612,7 +612,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 +627,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 +672,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 +689,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 +707,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 +741,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 +809,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 +836,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 4e18bbf50d4e2..10970a385966a 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 @@ -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 090c7ed9f3df3..062abd47d163a 100644 --- a/mypyc/test-data/irbuild-statements.test +++ b/mypyc/test-data/irbuild-statements.test @@ -651,11 +651,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 +701,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') @@ -756,7 +756,7 @@ def delList(): r3, r4, r5 :: ptr l :: list r6 :: object - r7 :: int32 + r7 :: i32 r8 :: bit L0: r0 = PyList_New(2) @@ -779,13 +779,13 @@ def delListMultiple(): r8, r9, r10, r11, r12, r13, r14, r15 :: ptr l :: list r16 :: object - r17 :: int32 + r17 :: i32 r18 :: bit r19 :: object - r20 :: int32 + r20 :: i32 r21 :: bit r22 :: object - r23 :: int32 + r23 :: i32 r24 :: bit L0: r0 = PyList_New(7) @@ -837,7 +837,7 @@ def delDict(): r2, r3 :: object r4, d :: dict r5 :: str - r6 :: int32 + r6 :: i32 r7 :: bit L0: r0 = 'one' @@ -855,9 +855,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 +901,7 @@ L0: def delAttribute(): r0, dummy :: __main__.Dummy r1 :: str - r2 :: int32 + r2 :: i32 r3 :: bit L0: r0 = Dummy(2, 4) @@ -913,10 +913,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) @@ -1029,7 +1029,7 @@ def f(a, b): r6, r7 :: object r8, x :: int r9, y :: bool - r10 :: int32 + r10 :: i32 r11 :: bit r12 :: bool r13 :: short_int @@ -1055,7 +1055,7 @@ L3: y = r9 r10 = PyObject_IsTrue(b) r11 = r10 >= 0 :: signed - r12 = truncate r10: int32 to builtins.bool + r12 = truncate r10: i32 to builtins.bool if r12 goto L4 else goto L5 :: bool L4: x = 2 diff --git a/mypyc/test-data/irbuild-str.test b/mypyc/test-data/irbuild-str.test index 63be7250ebd10..9851e0f4fb24d 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 diff --git a/mypyc/test-data/irbuild-strip-asserts.test b/mypyc/test-data/irbuild-strip-asserts.test index e90905dc5d814..25fd29818202e 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 faf3fa1dbd2f0..a5b7b9a55b868 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 6a86a6c6781b9..a47f3db6a7254 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -103,7 +103,7 @@ def f(x, y): r1, r2 :: object r3, r4, r5 :: ptr r6, r7, r8 :: object - r9 :: int32 + r9 :: i32 r10 :: bit r11 :: tuple L0: diff --git a/mypyc/test-data/irbuild-u8.test b/mypyc/test-data/irbuild-u8.test new file mode 100644 index 0000000000000..14f691c9451f2 --- /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 2c164491a5a12..1c024a249bf1c 100644 --- a/mypyc/test-data/irbuild-unreachable.test +++ b/mypyc/test-data/irbuild-unreachable.test @@ -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 diff --git a/mypyc/test-data/refcount.test b/mypyc/test-data/refcount.test index 372956a00cabb..3db4caa395665 100644 --- a/mypyc/test-data/refcount.test +++ b/mypyc/test-data/refcount.test @@ -702,7 +702,7 @@ def f(a, x): a :: list x :: int r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit L0: inc_ref x :: int @@ -1504,10 +1504,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-bools.test b/mypyc/test-data/run-bools.test index 522296592c54f..d7a2aa37ade76 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 92ec3873bf382..59617714f7e73 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 41675e7fcc911..58b862e3f3032 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 1b67a1190cd8b..2c101100549d5 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-i16.test b/mypyc/test-data/run-i16.test new file mode 100644 index 0000000000000..fbb0c15220bcb --- /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 af99fb79d35e1..bb1fa43bb9fd8 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 cd4ac19532d25..1a82ac3e2dd12 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 c6d5bdb3d8649..c5839d57820e1 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 c65f36110b46a..d575e141b5671 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 0000000000000..266b4851575f3 --- /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 267a3441808f1..c40e0fc55f0eb 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 @@ -1073,25 +1108,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 +for _ in range(2): + try: + import native + raise RuntimeError('exception expected') + except AssertionError: + pass [case testRepeatedUnderscoreFunctions] def _(arg): pass diff --git a/mypyc/test-data/run-multimodule.test b/mypyc/test-data/run-multimodule.test index 418af66ba0606..70c73dc2088b6 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 56c946933fac4..8d178d03a75bf 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 4a20c13ce7899..be668435d073f 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 f6c92b9c720fa..0851c15e57fdb 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 0000000000000..cddb031e33524 --- /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_emit.py b/mypyc/test/test_emit.py index 7351cd7fb13ee..e4ace3ec01f02 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 d7dcf3be532b5..ab1586bb22a8a 100644 --- a/mypyc/test/test_emitfunc.py +++ b/mypyc/test/test_emitfunc.py @@ -894,12 +894,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 +917,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_irbuild.py b/mypyc/test/test_irbuild.py index cb5e690eed55d..5b3f678d8f173 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 008963642272b..7f7063cdc5e60 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_run.py b/mypyc/test/test_run.py index 6a5ab87fca497..df9d44eab73fa 100644 --- a/mypyc/test/test_run.py +++ b/mypyc/test/test_run.py @@ -11,7 +11,7 @@ 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 @@ -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: @@ -242,7 +241,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 2b0298cadeda7..82990e6afd828 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 f414edd1a2bb9..ff2c05ad983e1 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 609ffc27385ea..796811a6363c6 100644 --- a/mypyc/test/testutil.py +++ b/mypyc/test/testutil.py @@ -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, diff --git a/mypyc/transform/exceptions.py b/mypyc/transform/exceptions.py index 2851955ff38fa..33dfeb693cf70 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/refcount.py b/mypyc/transform/refcount.py index 13f6a121e7f10..f2ab438f6576b 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 328b9bf159a1b..67201acb9b94f 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"] skip-magic-trailing-comma = true force-exclude = ''' ^/mypy/typeshed| @@ -27,14 +25,99 @@ 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 + +select = [ + "E", # pycodestyle (error) + "F", # pyflakes + "B", # flake8-bugbear + "I", # isort + "RUF100", # Unused noqa comments + "PGH004" # blanket noqa comments +] + +ignore = [ + "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 + "E203", # 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 +] + +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 +] + +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.isort] +combine-as-imports = true +extra-standard-library = ["typing_extensions"] + +[tool.check-manifest] +ignore = ["**/.readthedocs.yaml"] + +[tool.pytest.ini_options] +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 +# 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$', + '''^if __name__ == ['"]__main__['"]:$''', ] diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index b164c14b64145..0000000000000 --- 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 ade0a8adee5e9..66fade81ffab9 100755 --- a/runtests.py +++ b/runtests.py @@ -50,9 +50,7 @@ # Self type check "self": [executable, "-m", "mypy", "--config-file", "mypy_self_check.ini", "-p", "mypy"], # 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 +127,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 511f794474e7c..0000000000000 --- 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 516a639f3bb2b..bbb655ea45371 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): + 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=) @@ -166,6 +174,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,7 +186,6 @@ 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", @@ -192,7 +201,7 @@ def run(self): long_description=long_description, author="Jukka Lehtosalo", author_email="jukka.lehtosalo@iki.fi", - url="http://www.mypy-lang.org/", + url="https://www.mypy-lang.org/", license="MIT License", py_modules=[], ext_modules=ext_modules, @@ -211,22 +220,21 @@ 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", + "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", + "News": "https://mypy-lang.org/news.html", "Documentation": "https://mypy.readthedocs.io/en/stable/index.html", "Repository": "https://github.com/python/mypy", }, diff --git a/test-data/.flake8 b/test-data/.flake8 deleted file mode 100644 index df2f9caf8c947..0000000000000 --- 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 bf896e8cdfa37..833a52007f579 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 a1c3b50eeeab8..7782aba46492f 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 9d71311c4d826..c0cca79b85520 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 003a29a2ef670..0ed729e24e439 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 9d71311c4d826..c0cca79b85520 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 003a29a2ef670..0ed729e24e439 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 88ed99fb525e5..f49ab244c27eb 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 9d71311c4d826..c0cca79b85520 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 de0052886c57b..6cea6ed4292af 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 003a29a2ef670..0ed729e24e439 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 c7ff39c111791..4960ea0a9555a 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 9d71311c4d826..c0cca79b85520 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 de0052886c57b..6cea6ed4292af 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 003a29a2ef670..0ed729e24e439 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 35b38168f25eb..ce436beefe851 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 0000000000000..125816151ef8f --- /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"] +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 4948dc6a01df8..0000000000000 --- 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 0000000000000..5269c94320e10 --- /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"] +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 11bcfb11a1048..0000000000000 --- 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 0000000000000..cc464af75b178 --- /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"] +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 3dab731cada93..0000000000000 --- 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 0000000000000..d5275d1ed8b3f --- /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"] +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 a5d7df83eeea6..0000000000000 --- 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 0000000000000..8567af11152ed --- /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"] +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 4f0d0d954a735..0000000000000 --- 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_mypy_demo/pyproject.toml b/test-data/pybind11_mypy_demo/pyproject.toml index 878abe731b1b0..773d036e62f56 100644 --- a/test-data/pybind11_mypy_demo/pyproject.toml +++ b/test-data/pybind11_mypy_demo/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/src/main.cpp b/test-data/pybind11_mypy_demo/src/main.cpp index 5cedef391b2d3..ff0f93bf7017d 100644 --- a/test-data/pybind11_mypy_demo/src/main.cpp +++ b/test-data/pybind11_mypy_demo/src/main.cpp @@ -167,4 +167,4 @@ void bind_basics(py::module& basics) { PYBIND11_MODULE(pybind11_mypy_demo, m) { auto basics = m.def_submodule("basics"); bind_basics(basics); -} \ No newline at end of file +} diff --git a/test-data/unit/README.md b/test-data/unit/README.md index 6cf0b1bb26cf1..f2c727b435435 100644 --- a/test-data/unit/README.md +++ b/test-data/unit/README.md @@ -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 566bb92d6e188..dc64476beda63 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,6 +240,7 @@ f(GoodAlias) [out] [case testInstantiationAbstractsInTypeForVariables] +# flags: --no-strict-optional from typing import Type from abc import abstractmethod @@ -399,7 +420,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 +442,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 +530,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 +760,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 +830,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 +853,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 +879,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 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 0000000000000..89b3a863f8c75 --- /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 40efe2d2cece4..bcf55d84ff26f 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -291,7 +291,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 +313,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 @@ -944,14 +944,110 @@ 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: + # These are always critical / syntax issues: y = [await foo(x) for x in [1, 2, 3]] # E: "await" outside coroutine ("async def") 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] diff --git a/test-data/unit/check-basic.test b/test-data/unit/check-basic.test index c16b9e40122dd..408c3599672b9 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") @@ -229,12 +229,12 @@ reveal_type(__annotations__) # N: Revealed type is "builtins.dict[builtins.str, [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() @@ -242,8 +242,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") @@ -493,3 +493,13 @@ 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' diff --git a/test-data/unit/check-bound.test b/test-data/unit/check-bound.test index eb97bde32e1f6..1c713fd77c38d 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-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test index 8ae7f6555f9db..ab2f5f3f6b48d 100644 --- a/test-data/unit/check-class-namedtuple.test +++ b/test-data/unit/check-class-namedtuple.test @@ -325,7 +325,10 @@ 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]" + +# 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.pyi] diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index d5fb830487e85..957eb9214d7c6 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) @@ -975,13 +1019,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 +1063,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 +1080,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 +1090,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 +1144,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] @@ -1158,7 +1205,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 +1214,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 +1261,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 +1442,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 -- ---------- @@ -1911,8 +1965,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 +1979,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 +2024,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 +2143,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 +2739,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 +2795,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 +3077,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 +3519,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 +3602,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 +3620,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 +4130,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 +4290,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 +4546,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 +4632,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 +5000,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] @@ -4934,6 +5108,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 +5123,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 +6269,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 +6289,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 +6315,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 @@ -7537,10 +7725,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 "" +if object(): + reveal_type(B()) # N: Revealed type is "" +if object(): + reveal_type(C()) # N: Revealed type is "" +if object(): + reveal_type(D()) # N: Revealed type is "" [case testOverloadedNewAndInitNoReturn] from typing import NoReturn, overload @@ -7579,13 +7771,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 "" 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 "" 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 "" 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 "" reveal_type(D(1)) # N: Revealed type is "__main__.D" [case testClassScopeImportWithWrapperAndError] @@ -7614,13 +7813,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 +7903,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 +7917,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 +7954,58 @@ 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 diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test index 9691e6565689b..9d9a7d9ac039d 100644 --- a/test-data/unit/check-columns.test +++ b/test-data/unit/check-columns.test @@ -328,7 +328,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 +386,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 beb1afd779c04..1eefdd3c66c1b 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 d7beea0390e7f..ec5bce219dbd7 100644 --- a/test-data/unit/check-custom-plugin.test +++ b/test-data/unit/check-custom-plugin.test @@ -887,7 +887,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 @@ -1014,3 +1017,15 @@ reveal_type(MyClass.foo_staticmethod) # N: Revealed type is "def (builtins.int) [file mypy.ini] \[mypy] plugins=/test-data/unit/plugins/add_classmethod.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 2a7fad1da9929..9029582ece82a 100644 --- a/test-data/unit/check-dataclass-transform.test +++ b/test-data/unit/check-dataclass-transform.test @@ -279,8 +279,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 +328,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 +447,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 +487,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[, ...]"; 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[, ...]"; 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[, ...]"; 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[, ...]"; 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[, ...]"; 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[, ...]"; 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') # E: Argument 1 to "replace" has incompatible type "Person"; expected a dataclass + +[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 4d85be3911866..adcaa60a5b192 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -198,7 +198,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 @@ -740,6 +744,17 @@ 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 @@ -1543,6 +1558,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 @@ -2001,3 +2045,430 @@ 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" + +[case testReplaceUnion] +# flags: --strict-optional +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) = + w: dict # exercises meet(dict, ) = + 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 +_ = 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[, ]"; expected +_ = 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/dataclasses.pyi] + +[case testReplaceUnionOfTypeVar] +# flags: --strict-optional +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: Argument 1 to "replace" has type "Union[TA, TB, int]" whose item "TB" is not bound to a dataclass # E: Argument 1 to "replace" has incompatible type "Union[TA, TB, int]" whose item "int" is not a dataclass + +[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: Argument 1 to "replace" has a variable type "TInt" not bound to a dataclass + +def f2(t: TAny) -> TAny: + return replace(t, x='spam') # E: Argument 1 to "replace" has a variable type "TAny" not bound to a dataclass + +def f3(t: TNone) -> TNone: + return replace(t, x='spam') # E: Argument 1 to "replace" has a variable type "TNone" not bound to a dataclass + +def f4(t: TUnion) -> TUnion: + return replace(t, x='spam') # E: Argument 1 to "replace" has incompatible type "TUnion" whose item "str" is not a dataclass # E: Argument 1 to "replace" has incompatible type "TUnion" whose item "int" is not a dataclass + +[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)) + +[case testReplaceAny] +from dataclasses import replace +from typing import Any + +a: Any +a2 = replace(a) +reveal_type(a2) # N: Revealed type is "Any" + +[case testReplaceNotDataclass] +from dataclasses import replace + +replace(5) # E: Argument 1 to "replace" has incompatible type "int"; expected a dataclass + +class C: + pass + +replace(C()) # E: Argument 1 to "replace" has incompatible type "C"; expected a dataclass + +replace(None) # E: Argument 1 to "replace" has incompatible type "None"; expected a dataclass + +[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]" + +[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] diff --git a/test-data/unit/check-dynamic-typing.test b/test-data/unit/check-dynamic-typing.test index dd4cc1579639e..0dc05a7a0ea13 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 80a7ca7ff99f7..ce7e173f635d4 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" @@ -1345,7 +1345,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 +1367,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] diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 8b3567ab7cf66..1e7dc9364855a 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,9 +114,11 @@ 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 @@ -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: @@ -805,6 +807,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 @@ -882,11 +900,11 @@ if any_or_object: # 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 @@ -960,7 +978,11 @@ 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] +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] 1 + "asdf" [case testShowErrorCodesInConfig] @@ -1050,4 +1072,8 @@ 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] diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 49a3f0d4aaa7b..8231b0a3265f5 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] @@ -1001,7 +1059,8 @@ 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 if int(): @@ -1038,7 +1097,7 @@ def f() -> None: pass class A: def __add__(self, x: 'A') -> 'A': pass -a = None # type: A +a: 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 @@ -1056,7 +1115,8 @@ class A: def __add__(self, x: 'A') -> 'A': pass -a, b = None, None # type: (A, bool) +a: A +b: 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 @@ -1073,7 +1133,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 +1160,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 +1169,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:] @@ -1134,7 +1212,7 @@ a[:None] [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 +1241,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 +1271,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 +1280,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 +1289,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 +1312,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 +1327,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 +1341,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 +1355,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 +1372,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 +1392,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 +1404,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] @@ -1422,14 +1501,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 +1522,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 +1531,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 +1562,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 = ... @@ -1769,13 +1849,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] diff --git a/test-data/unit/check-fastparse.test b/test-data/unit/check-fastparse.test index f172a9727d49e..132a34503b896 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 @@ -228,8 +228,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-flags.test b/test-data/unit/check-flags.test index 0ac39ebf9c109..c356028f66204 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -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 @@ -2101,12 +2101,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] @@ -2174,3 +2174,39 @@ 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] diff --git a/test-data/unit/check-formatting.test b/test-data/unit/check-formatting.test index 5c0d0ed652925..f63abbb330348 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] @@ -125,14 +130,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] @@ -435,21 +457,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) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index c23bbb77f6435..0de4798ea1f51 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) @@ -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 @@ -2612,7 +2618,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 +2731,432 @@ TS = TypeVar("TS", bound=str) f: Callable[[Sequence[TI]], None] g: Callable[[Union[Sequence[TI], Sequence[TS]]], None] f = g + +[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] diff --git a/test-data/unit/check-functools.test b/test-data/unit/check-functools.test index f95b823a5291a..ebfe86f2b2416 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,7 +99,6 @@ 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] @@ -151,5 +144,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-subtyping.test b/test-data/unit/check-generic-subtyping.test index 1f06bc7c540ad..11c92d07021a2 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 @@ -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 a62028ca94ea7..42e3d23eddb9a 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 @@ -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] @@ -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]" @@ -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]") @@ -1282,9 +1294,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 +1325,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 +1347,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 +1392,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 +1405,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 +1417,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 +1436,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 +1465,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") @@ -2698,3 +2712,285 @@ def func(var: T) -> T: reveal_type(func(1)) # N: Revealed type is "builtins.int" [builtins fixtures/tuple.pyi] + +[case testGenericLambdaGenericMethodNoCrash] +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]: ... + +f(lambda x: x.g(0)) # E: Cannot infer type argument 1 of "f" + +[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" +# TODO: support this situation +reveal_type(dec2(test2)) # N: Revealed type is "def (builtins.object) -> def (builtins.object) -> builtins.object" +[builtins fixtures/paramspec.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: --strict-optional --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: --strict-optional --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: --strict-optional --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" diff --git a/test-data/unit/check-ignore.test b/test-data/unit/check-ignore.test index 982fb67f4e7f8..fa451f373e704 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 ec0c5d5e48058..cd009887a5b5b 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' @@ -2916,8 +2916,8 @@ main:3: 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 +2928,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 +2945,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 +2955,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 +2969,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,16 +3005,16 @@ 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] [rechecked] @@ -3069,7 +3068,7 @@ from attr import attrib, attrs class A: a: int -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [rechecked] [stale] [out2] @@ -3092,22 +3091,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 +3120,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 +3151,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 +3183,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 +3253,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 +3340,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 +3365,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 +3382,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 +3403,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 +3422,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 @@ -3699,8 +3698,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 +3740,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 +3750,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 +3763,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 +3775,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] @@ -5414,7 +5413,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 +5445,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." @@ -6403,3 +6404,29 @@ 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] diff --git a/test-data/unit/check-inference-context.test b/test-data/unit/check-inference-context.test index 625ab091a6a99..59f5154909648 100644 --- a/test-data/unit/check-inference-context.test +++ b/test-data/unit/check-inference-context.test @@ -13,9 +13,9 @@ 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() @@ -28,9 +28,9 @@ 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() @@ -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,9 +367,9 @@ 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") @@ -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,13 +693,13 @@ f(lambda: None) g(lambda: None) [case testIsinstanceInInferredLambda] -from typing import TypeVar, Callable +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 +def f(func: Callable[[T], S], *z: T, r: Optional[S] = None) -> S: pass f(lambda x: 0 if isinstance(x, B) else 1) # E: Cannot infer type argument 1 of "f" 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 @@ -739,7 +745,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 +770,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 +788,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 +808,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 +829,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] @@ -847,7 +855,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 @@ -913,7 +921,7 @@ 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] @@ -941,7 +949,7 @@ 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] diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index fc8113766f1a9..ee13cb3830fcd 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -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,8 @@ 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(): @@ -388,6 +504,37 @@ 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(): + 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 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" @@ -884,7 +1036,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 +1047,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 +1120,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 +1140,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 +1162,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") @@ -1087,7 +1245,7 @@ class B: pass [case testMultipleAssignmentWithPartialDefinition] -a = None # type: A +a: A if int(): x, a = a, a if int(): @@ -1099,7 +1257,7 @@ if int(): class A: pass [case testMultipleAssignmentWithPartialDefinition2] -a = None # type: A +a: A if int(): a, x = [a, a] if int(): @@ -1113,7 +1271,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 +1286,7 @@ class A: pass class A: pass class B: pass -if A: +if int(): a = A() if int(): a = A() @@ -1233,6 +1391,7 @@ main:4: error: Cannot infer type argument 1 of "f" main:4: error: 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 @@ -1295,8 +1454,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 +1476,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 +1493,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 +1516,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 +1683,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] @@ -1766,6 +1927,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 +1950,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 +1967,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 +1979,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 +1994,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 @@ -2190,8 +2356,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 diff --git a/test-data/unit/check-inline-config.test b/test-data/unit/check-inline-config.test index 1b2085e33e91b..71030b5c9b972 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] @@ -192,7 +193,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 +211,117 @@ 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] +# flags: --strict-optional + +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 0722ee8d91e5a..3403e726d8b57 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: ... @@ -2670,10 +2671,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 +2683,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 e0fe389bbbd9f..b3ee47aa6fdfd 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,7 +344,7 @@ 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] @@ -357,9 +364,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 +375,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 +387,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 +399,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 +506,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 +523,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 +536,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 +545,7 @@ foo(**b) foo(**c) foo(**d) foo(**e) +foo(**f) # Correct: @@ -544,9 +559,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 899b7c5b209fc..9809024afdbbe 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] diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index d523e5c08af83..abdbf733a6797 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -19,7 +19,7 @@ reveal_type(g2) # N: Revealed type is "def (x: Literal['A B'])" [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 @@ -753,16 +753,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] @@ -1760,7 +1760,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,12 +1794,15 @@ 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 "" 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] @@ -2013,7 +2016,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 +2069,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" diff --git a/test-data/unit/check-lowercase.test b/test-data/unit/check-lowercase.test new file mode 100644 index 0000000000000..d1ebbdd282fa2 --- /dev/null +++ b/test-data/unit/check-lowercase.test @@ -0,0 +1,51 @@ + +[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") diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 4b8308310ae67..fc3daff64fbd7 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] @@ -584,6 +586,7 @@ x = 1+0 [case testConditionalImportAndAssign] +# flags: --no-strict-optional try: from m import x except: @@ -675,6 +678,7 @@ class B(A): ... [case testImportVariableAndAssignNone] +# flags: --no-strict-optional try: from m import x except: @@ -683,6 +687,7 @@ except: x = 1 [case testImportFunctionAndAssignNone] +# flags: --no-strict-optional try: from m import f except: @@ -708,6 +713,7 @@ except: def f(): pass [case testAssignToFuncDefViaGlobalDecl2] +# flags: --no-strict-optional import typing from m import f def g() -> None: @@ -720,6 +726,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 +736,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 +745,7 @@ def f(): pass [out] [case testConditionalImportAndAssignNoneToModule] +# flags: --no-strict-optional if object(): import m else: @@ -757,6 +766,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 +939,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 +1510,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 +1541,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 +1570,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] @@ -3216,7 +3226,6 @@ class Bar(Foo): from a import Foo class Bar(Foo): def frobnicate(self, *args) -> None: pass # type: ignore[override] # I know -[builtins fixtures/tuple.pyi] [builtins fixtures/dict.pyi] [out1] tmp/b.py:3: error: Signature of "frobnicate" incompatible with supertype "Foo" diff --git a/test-data/unit/check-multiple-inheritance.test b/test-data/unit/check-multiple-inheritance.test index a8d053f9504e7..d03f2e35e1c46 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 6b9f139f541cc..83cc8c099deba 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] @@ -32,7 +32,7 @@ X = namedtuple('X', 'x, _y, _z') # E: "namedtuple()" field names cannot start w 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" @@ -63,13 +63,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 @@ -292,7 +292,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 +357,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 +366,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 +391,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 +405,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 +423,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 +459,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 +472,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 +482,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 +499,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 +511,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] @@ -938,8 +938,9 @@ 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 testNamedTupleWrongfile] from typing import NamedTuple @@ -983,7 +984,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 @@ -1337,3 +1338,12 @@ 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] diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index f05e2aaf5c197..c329ccf840a8d 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -703,47 +703,47 @@ 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] @@ -795,6 +795,7 @@ reveal_type(x_union) # N: Revealed type is "Union[Literal['A'], Literal['B' [case testNarrowingEqualityRequiresExplicitEnumLiteral] # flags: --strict-optional +from typing import Union from typing_extensions import Literal, Final from enum import Enum @@ -805,19 +806,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 +826,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] diff --git a/test-data/unit/check-native-int.test b/test-data/unit/check-native-int.test index 1e945d0af27d3..1129512694f4d 100644 --- a/test-data/unit/check-native-int.test +++ b/test-data/unit/check-native-int.test @@ -87,8 +87,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 "" +if object(): + reveal_type(meet(f64, f32)) # N: Revealed type is "" reveal_type(meet(f, fa)) # N: Revealed type is "builtins.int" reveal_type(meet(f32, fa)) # N: Revealed type is "mypy_extensions.i32" @@ -148,8 +150,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 "" +if object(): + reveal_type(meet(fi32, ff)) # N: Revealed type is "" [builtins fixtures/dict.pyi] [case testNativeIntForLoopRange] diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 99f4141a4d649..77a1553d47155 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -992,7 +992,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 +1009,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] @@ -2149,6 +2149,7 @@ x: C class C: def frob(self, foos: Dict[Any, Foos]) -> None: foo = foos.get(1) + assert foo dict(foo) [builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-newsyntax.test b/test-data/unit/check-newsyntax.test index 63284c34bd8bc..cfcbfc598c514 100644 --- a/test-data/unit/check-newsyntax.test +++ b/test-data/unit/check-newsyntax.test @@ -105,11 +105,6 @@ main:5: error: "str" has no attribute "x" 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 - [case testNewSyntaxFStringBasics] # flags: --python-version 3.6 f'foobar' diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 754c6b52ff196..ae247b0047f15 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -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 4209f4ec91640..4851cc96e6da9 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(): @@ -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 @@ -4745,7 +4758,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 +5418,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 +5744,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 +5764,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 +5784,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 +5800,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 +5831,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 +5842,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 +5852,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 +5871,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 +5894,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 +5916,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 +5933,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 +5949,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 +5959,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 +6202,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 +6221,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 +6242,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 +6266,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 +6422,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 +6448,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 diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index 56fc3b6faa14e..3d05faed74f17 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] @@ -539,7 +539,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 +570,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: --python-version 3.8 --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,8 +592,6 @@ 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 @@ -621,9 +618,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 @@ -663,8 +657,6 @@ 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]]" [case testSolveParamSpecWithSelfType] @@ -1041,6 +1033,28 @@ reveal_type(jf) # N: Revealed type is "def [_T] (x: _T`-1)" 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`-1], _T`-1]" + +jf = j.into_callable() +reveal_type(jf) # N: Revealed type is "def [_T] (x: _T`-1) -> _T`-1" +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 @@ -1471,3 +1485,68 @@ 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 [U] (x: U`-1) -> builtins.list[U`-1]" +[builtins fixtures/paramspec.pyi] diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-plugin-attrs.test similarity index 82% rename from test-data/unit/check-attr.test rename to test-data/unit/check-plugin-attrs.test index f6ef289e792e1..348c8243ca8ec 100644 --- a/test-data/unit/check-attr.test +++ b/test-data/unit/check-plugin-attrs.test @@ -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] @@ -400,20 +400,25 @@ 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] @@ -469,6 +474,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 +672,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 +688,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 +976,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 @@ -1118,12 +1175,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[]", 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] @@ -1158,7 +1216,7 @@ class A: A(None, None) -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsOptionalConverterNewPackage] # flags: --strict-optional @@ -1178,7 +1236,7 @@ class A: A(None, None) -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsTypeVarNoCollision] @@ -1191,7 +1249,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 +1259,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 +1269,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 +1278,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 +1288,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 +1298,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 +1310,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 +1325,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 @@ -1441,7 +1499,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 +1514,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 +1529,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 +1554,7 @@ 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 testAttrsInitMethodAlwaysGenerates] from typing import Tuple @@ -1514,11 +1572,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 +1620,34 @@ 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 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 +1663,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 +1684,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 +1707,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 +1721,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 +1737,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 +1751,7 @@ class D(Generic[T]): class C: pass -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testComplexTypeInAttrIb] import a @@ -1664,8 +1767,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 @@ -1829,7 +1930,11 @@ 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" @@ -1867,3 +1972,229 @@ 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) = + w: dict # exercises meet(dict, ) = + + +@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 +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[, ]"; expected + +[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] diff --git a/test-data/unit/check-possibly-undefined.test b/test-data/unit/check-possibly-undefined.test index 29c4868e97aff..ebceef88b5378 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 - -def f1() -> None: - a = x # E: Name "x" is used before definition - x: int = 1 +a = z # No error. +z: int = 1 -def f2() -> None: - a = x.y # E: Name "x" is used before definition - x: Any = 1 +a = x # E: Name "x" is used before definition +x: int = 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 diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index c787b34bf26b2..5d5ba54304a3a 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -319,7 +319,9 @@ 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 @@ -1263,13 +1265,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 +1288,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 +1443,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 +1637,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 +2284,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 +2401,7 @@ x: P = None [out] [case testNoneSubtypeOfAllProtocolsWithoutStrictOptional] +# flags: --no-strict-optional from typing import Protocol class P(Protocol): attr: int @@ -2790,6 +2794,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 @@ -3191,7 +3259,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 +3591,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 +3612,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 +4099,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 7a934348aaf2f..6416fa02bbce7 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -1841,3 +1841,140 @@ 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] +# flags: --strict-optional +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] diff --git a/test-data/unit/check-python311.test b/test-data/unit/check-python311.test index 7196f10f88632..5870c7e17bccf 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,17 @@ 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] diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index b9f9f2173ae17..423daaf5ae8fa 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -310,7 +310,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 @@ -775,3 +776,55 @@ main:9: note: Revealed type is "builtins.int" 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] +# flags: --strict-optional +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} + ^~~~~ diff --git a/test-data/unit/check-python39.test b/test-data/unit/check-python39.test index 105051a840bbf..e17bf1e7ab5b9 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 b7b4372ecc12f..dc1ae448c0d14 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]" @@ -897,3 +897,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 555cef3641f8e..d366e7c337992 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] @@ -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 @@ -1805,3 +1938,38 @@ 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] diff --git a/test-data/unit/check-serialize.test b/test-data/unit/check-serialize.test index 66d5d879ae681..e5d1d6b170f91 100644 --- a/test-data/unit/check-serialize.test +++ b/test-data/unit/check-serialize.test @@ -1275,6 +1275,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 8fe049437c57d..1bc34c6fdaabf 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) @@ -180,9 +180,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] diff --git a/test-data/unit/check-slots.test b/test-data/unit/check-slots.test index 8beb0d8bf3f74..b7ce5e596101a 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 b9551870ddfc0..023e2935a1581 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -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(): @@ -2212,3 +2248,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 b3379e505be72..48a0a0250ecf5 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') diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 266bfbf97888f..5cb89a6854be7 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]]") @@ -108,8 +109,8 @@ class B(A): pass [case testSubtypingWithNamedTupleType] from typing import Tuple -t1 = None # type: Tuple[A, A] -t2 = None # type: tuple +t1: Tuple[A, A] +t2: tuple if int(): t1 = t2 # E: Incompatible types in assignment (expression has type "Tuple[Any, ...]", variable has type "Tuple[A, A]") @@ -120,6 +121,7 @@ class A: pass [builtins fixtures/tuple.pyi] [case testTupleInitializationWithNone] +# flags: --no-strict-optional from typing import Tuple t = None # type: Tuple[A, A] t = None @@ -132,6 +134,7 @@ class A: pass [case testTupleExpressions] +# flags: --no-strict-optional from typing import Tuple t1 = None # type: tuple t2 = None # type: Tuple[A] @@ -177,12 +180,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(): @@ -221,9 +225,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 +256,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 +270,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 +297,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 +329,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 +341,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 +361,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 +379,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 +395,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 +408,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 +429,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 +440,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 +468,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 +501,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 +513,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 +527,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 +553,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 +565,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 +585,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 +600,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 +612,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 +627,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 +668,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 +688,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 +732,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 +742,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 +758,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() @@ -869,7 +887,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 +950,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] @@ -1046,8 +1064,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 +1081,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 +1101,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 +1121,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 +1146,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 +1159,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 +1186,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, ...]" @@ -1248,7 +1266,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] @@ -1367,42 +1385,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] diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index d7cccd2d6ba6e..3bfcf6a9afeae 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -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__] @@ -169,11 +169,11 @@ 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 +x: EmptyTuple reveal_type(x) # N: Revealed type is "Tuple[]" EmptyTupleCallable = Callable[[Tuple[()]], None] -f = None # type: EmptyTupleCallable +f: EmptyTupleCallable reveal_type(f) # N: Revealed type is "def (Tuple[])" [builtins fixtures/list.pyi] @@ -1028,3 +1028,34 @@ 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.pyi] diff --git a/test-data/unit/check-type-checks.test b/test-data/unit/check-type-checks.test index 106f2d680ba45..03c8de4177f3b 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 e3d6188b643b9..739d1ba6eb751 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 @@ -1121,8 +1121,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] @@ -1642,7 +1642,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 +1740,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" @@ -1805,7 +1805,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] @@ -2873,3 +2873,376 @@ 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] diff --git a/test-data/unit/check-typeguard.test b/test-data/unit/check-typeguard.test index 39bcb091f09e4..a307e4c8b6a06 100644 --- a/test-data/unit/check-typeguard.test +++ b/test-data/unit/check-typeguard.test @@ -602,12 +602,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 ... -def bad_typeguard(*, x: object) -> TypeGuard[int]: # E: TypeGuard functions must have a positional argument + @staticmethod + def typeguard2(x: object) -> TypeGuard[int]: + ... + + @staticmethod # line 11 + def typeguard3(*, x: object) -> TypeGuard[int]: + ... + +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 +654,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 +684,14 @@ 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] diff --git a/test-data/unit/check-typevar-defaults.test b/test-data/unit/check-typevar-defaults.test new file mode 100644 index 0000000000000..514186aa7518f --- /dev/null +++ b/test-data/unit/check-typevar-defaults.test @@ -0,0 +1,83 @@ +[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, str]) # 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 diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 9afe709ed19b8..1024f90ee6b7d 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -17,7 +17,8 @@ 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 +if object(): + f(0) # E: Argument 1 to "f" has incompatible type "int"; expected def g(a: Tuple[Unpack[Ts]], b: Tuple[Unpack[Ts]]) -> Tuple[Unpack[Ts]]: return a @@ -117,8 +118,7 @@ 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[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]" @@ -202,7 +202,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]]: ... @@ -238,7 +238,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]]: ... @@ -444,7 +444,7 @@ def foo(*args: Unpack[Tuple[int, ...]]) -> None: 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 "Unpack[Tuple[int, ...]]" def foo2(*args: Unpack[Tuple[str, Unpack[Tuple[int, ...]], bool, bool]]) -> None: @@ -512,3 +512,296 @@ 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 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: + ... + # TODO: exposes unhandled case in checkexpr + # 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[[VarArg(object)], 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[[VarArg(object)], None]" +call(A().func, 0) # E: Argument 1 to "call" has incompatible type "Callable[[int, str], None]"; expected "Callable[[VarArg(object)], None]" +call(A().func, 0, 1) # E: Argument 1 to "call" has incompatible type "Callable[[int, str], None]"; expected "Callable[[int, object], 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]] +B = A[Unpack[Ts]] +x: B[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 testVariadicAliasWrongCallable] +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], int] # E: Required positional args may not appear after default, named or var args +x: A[int, str, int, str] +reveal_type(x) # N: Revealed type is "def (builtins.int, builtins.str, builtins.int, builtins.str) -> builtins.int" +[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: Var args may not appear after named or var args \ + # 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, *Unpack[builtins.tuple[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, *Unpack[builtins.tuple[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] diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index a4a4d68bd9fe0..effaf620f1f0b 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-union-error-syntax.test b/test-data/unit/check-union-error-syntax.test new file mode 100644 index 0000000000000..2928cc3127096 --- /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-unions.test b/test-data/unit/check-unions.test index 65d5c1abc7e8c..28d83aa54cccd 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" @@ -1010,6 +1013,7 @@ MYTYPE = List[Union[str, "MYTYPE"]] # E: Cannot resolve name "MYTYPE" (possible [builtins fixtures/list.pyi] [case testNonStrictOptional] +# flags: --no-strict-optional from typing import Optional, List def union_test1(x): diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 48459dd8941a3..1db2a16e2e1c7 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -873,15 +873,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 +890,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] @@ -936,7 +936,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: @@ -1423,3 +1423,27 @@ 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] diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index d598fe13b7e96..4da9e0e5033ef 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,9 +46,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(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" @@ -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]]: @@ -649,7 +663,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 +776,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: "Person" cannot be unpacked (must be tuple or TypeVarTuple) ... -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 ... diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index c2e98cdb74f91..6e9fdf6dab654 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -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 ^~~~ @@ -1484,6 +1463,10 @@ 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? @@ -1523,6 +1506,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] diff --git a/test-data/unit/daemon.test b/test-data/unit/daemon.test index 7586c8763d333..f208b4e78e54b 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 @@ -311,8 +360,8 @@ 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 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) @@ -374,7 +423,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 @@ -429,7 +478,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 +520,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 diff --git a/test-data/unit/deps-types.test b/test-data/unit/deps-types.test index 1d7064cde0c7b..def117fe04df0 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 28d51f1a4c30b..b43a2ace5eeda 100644 --- a/test-data/unit/deps.test +++ b/test-data/unit/deps.test @@ -1388,6 +1388,7 @@ class B(A): -> , m -> -> , m.B.__init__ + -> , m.B.__mypy-replace -> -> -> @@ -1419,6 +1420,7 @@ class B(A): -> -> , m.B.__init__ -> + -> , m.B.__mypy-replace -> -> -> diff --git a/test-data/unit/envvars.test b/test-data/unit/envvars.test index 0d78590e57a52..8832f80cff3cf 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 3fd40b774c7b4..8606fea15849d 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 a134fb1d43011..33dedd8871142 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 50f93dd35af34..00157333efd7e 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 0000000000000..cc297bc344aa6 --- /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:13: note: "B" defined here +main:7: error: Unexpected keyword argument "y" for "B" +builtins.pyi:13: 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 0000000000000..036d858ddf69f --- /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-modules.test b/test-data/unit/fine-grained-modules.test index f76ced64341b2..163e859276cb9 100644 --- a/test-data/unit/fine-grained-modules.test +++ b/test-data/unit/fine-grained-modules.test @@ -1279,12 +1279,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 +1294,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 +1808,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 a28e3204b93f7..47de16b8d765d 100644 --- a/test-data/unit/fine-grained-suggest.test +++ b/test-data/unit/fine-grained-suggest.test @@ -1090,7 +1090,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 58339828677d9..11a8f03590f79 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] @@ -989,7 +984,6 @@ import attr class A: a: int other: int -[builtins fixtures/list.pyi] [file a.py.3] import attr @@ -1048,7 +1042,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 +1724,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] @@ -4632,6 +4626,7 @@ class User: == [case testNoStrictOptionalModule] +# flags: --no-strict-optional import a a.y = a.x [file a.py] @@ -4649,9 +4644,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 +4668,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,7 +4696,7 @@ 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 @@ -5282,10 +5279,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 +5305,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): @@ -7547,7 +7546,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 +7774,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 +7787,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 @@ -9115,27 +9115,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 +9667,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 +9699,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." @@ -9873,7 +9875,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 @@ -10102,23 +10104,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 +10326,24 @@ 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 diff --git a/test-data/unit/fixtures/args.pyi b/test-data/unit/fixtures/args.pyi index 9985ccf848178..0020d9ceff468 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 e9394c84ba7de..059c853a621fa 100644 --- a/test-data/unit/fixtures/dataclasses.pyi +++ b/test-data/unit/fixtures/dataclasses.pyi @@ -1,3 +1,4 @@ +import _typeshed from typing import ( Generic, Iterator, Iterable, Mapping, Optional, Sequence, Tuple, TypeVar, Union, overload, @@ -46,4 +47,5 @@ class list(Generic[_T], Sequence[_T]): class function: pass class classmethod: pass +class staticmethod: pass property = object() diff --git a/test-data/unit/fixtures/dict.pyi b/test-data/unit/fixtures/dict.pyi index 153832411f50c..19d175ff79aba 100644 --- a/test-data/unit/fixtures/dict.pyi +++ b/test-data/unit/fixtures/dict.pyi @@ -1,5 +1,7 @@ # Builtins stub used in dictionary-related test cases. +from _typeshed import SupportsKeysAndGetItem +import _typeshed from typing import ( TypeVar, Generic, Iterable, Iterator, Mapping, Tuple, overload, Optional, Union, Sequence ) @@ -25,7 +27,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 diff --git a/test-data/unit/fixtures/module_all.pyi b/test-data/unit/fixtures/module_all.pyi index b14152c7e98f2..d6060583b20e1 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/paramspec.pyi b/test-data/unit/fixtures/paramspec.pyi index 0686924aad6f4..5e4b8564e238d 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 diff --git a/test-data/unit/fixtures/attr.pyi b/test-data/unit/fixtures/plugin_attrs.pyi similarity index 90% rename from test-data/unit/fixtures/attr.pyi rename to test-data/unit/fixtures/plugin_attrs.pyi index 3bd4f0ec7cbef..f62104809e74d 100644 --- a/test-data/unit/fixtures/attr.pyi +++ b/test-data/unit/fixtures/plugin_attrs.pyi @@ -1,4 +1,4 @@ -# Builtins stub used to support @attr.s tests. +# Builtins stub used to support attrs plugin tests. from typing import Union, overload class object: @@ -9,13 +9,13 @@ class object: 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: ... diff --git a/test-data/unit/fixtures/primitives.pyi b/test-data/unit/fixtures/primitives.pyi index 90d76b9d76dd1..b74252857d6f6 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') diff --git a/test-data/unit/fixtures/property.pyi b/test-data/unit/fixtures/property.pyi index 2397c05c78d50..667bdc02d0f58 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 b5a4549da0686..b22a12b5213f2 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 0261731304b12..e270f3d79d3ec 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") diff --git a/test-data/unit/fixtures/typing-async.pyi b/test-data/unit/fixtures/typing-async.pyi index b061337845c2d..b207dd599c333 100644 --- a/test-data/unit/fixtures/typing-async.pyi +++ b/test-data/unit/fixtures/typing-async.pyi @@ -108,6 +108,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 1471473249dca..417ae6baf491b 100644 --- a/test-data/unit/fixtures/typing-full.pyi +++ b/test-data/unit/fixtures/typing-full.pyi @@ -136,6 +136,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 @@ -190,3 +191,4 @@ def dataclass_transform( field_specifiers: tuple[type[Any] | Callable[..., Any], ...] = ..., **kwargs: Any, ) -> Callable[[T], T]: ... +def override(__arg: T) -> T: ... diff --git a/test-data/unit/fixtures/typing-medium.pyi b/test-data/unit/fixtures/typing-medium.pyi index 863b0703989d0..03be1d0a664d1 100644 --- a/test-data/unit/fixtures/typing-medium.pyi +++ b/test-data/unit/fixtures/typing-medium.pyi @@ -55,6 +55,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 1a31549463b69..f4744575fc092 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 0000000000000..606ca63d4f0d8 --- /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.pyi b/test-data/unit/fixtures/typing-typeddict.pyi index 92ae402b9ea58..24a2f13289812 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 43114a8af004d..15b1065cb7a90 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 0000000000000..054ad0ec0c464 --- /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 795e5d3f4f697..1a3838aa3ab1f 100644 --- a/test-data/unit/lib-stub/attr/__init__.pyi +++ b/test-data/unit/lib-stub/attr/__init__.pyi @@ -244,3 +244,6 @@ def field( order: Optional[bool] = ..., on_setattr: Optional[object] = ..., ) -> Any: ... + +def evolve(inst: _T, **changes: Any) -> _T: ... +def assoc(inst: _T, **changes: Any) -> _T: ... diff --git a/test-data/unit/lib-stub/attrs/__init__.pyi b/test-data/unit/lib-stub/attrs/__init__.pyi index d257740451324..bf31274b3eb99 100644 --- a/test-data/unit/lib-stub/attrs/__init__.pyi +++ b/test-data/unit/lib-stub/attrs/__init__.pyi @@ -1,4 +1,6 @@ -from typing import TypeVar, overload, Callable, Any, Optional, Union, Sequence, Mapping +from typing import TypeVar, overload, Callable, Any, Optional, Union, Sequence, Mapping, Generic + +from attr import Attribute as Attribute _T = TypeVar('_T') _C = TypeVar('_C', bound=type) @@ -126,3 +128,6 @@ def field( order: Optional[bool] = ..., on_setattr: Optional[object] = ..., ) -> Any: ... + +def evolve(inst: _T, **changes: Any) -> _T: ... +def assoc(inst: _T, **changes: Any) -> _T: ... diff --git a/test-data/unit/lib-stub/builtins.pyi b/test-data/unit/lib-stub/builtins.pyi index c2ac78c416615..17d519cc8eeae 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 e2a0cccd562ad..ca9e91cf4d65b 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 bd33b459266c1..b2b48c2ae4862 100644 --- a/test-data/unit/lib-stub/dataclasses.pyi +++ b/test-data/unit/lib-stub/dataclasses.pyi @@ -32,3 +32,5 @@ def field(*, class Field(Generic[_T]): pass + +def replace(__obj: _T, **changes: Any) -> _T: ... diff --git a/test-data/unit/lib-stub/math.pyi b/test-data/unit/lib-stub/math.pyi new file mode 100644 index 0000000000000..06f8878a563e9 --- /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 d79be8719417c..4295c33f81ade 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 0000000000000..fad173c9a8b66 --- /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 a306b70f74d74..b35b64a383c94 100644 --- a/test-data/unit/lib-stub/typing.pyi +++ b/test-data/unit/lib-stub/typing.pyi @@ -50,7 +50,9 @@ class Sequence(Iterable[T_co]): def __getitem__(self, n: Any) -> T_co: 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 22b8959715212..216005e3cf837 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, Mapping, Iterable, Iterator, NoReturn as NoReturn, Dict, Tuple, Type from typing import TYPE_CHECKING as TYPE_CHECKING from typing import NewType as NewType, overload as overload @@ -50,9 +50,16 @@ 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] + __total__: bool def TypedDict(typename: str, fields: Dict[str, Type[_T]], *, total: Any = ...) -> Type[dict]: ... @@ -67,4 +74,6 @@ def dataclass_transform( **kwargs: Any, ) -> Callable[[T], T]: ... +def override(__arg: _T) -> _T: ... + _FutureFeatureFixture = 0 diff --git a/test-data/unit/merge.test b/test-data/unit/merge.test index 144a095440f2e..19b1839f86c0d 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/parse-errors.test b/test-data/unit/parse-errors.test index 9123ce0cf5092..33cf9b4f91b42 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,7 +269,7 @@ 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 diff --git a/test-data/unit/parse.test b/test-data/unit/parse.test index ff892ce0ce05a..10ceaa947fd4f 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 7167d97487c18..e8ebbd03dca7b 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 @@ -222,4 +204,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/custom_errorcode.py b/test-data/unit/plugins/custom_errorcode.py new file mode 100644 index 0000000000000..0e2209a32ecab --- /dev/null +++ b/test-data/unit/plugins/custom_errorcode.py @@ -0,0 +1,20 @@ +from mypy.errorcodes import ErrorCode +from mypy.plugin import Plugin +from mypy.types import AnyType, TypeOfAny + +CUSTOM_ERROR = ErrorCode(code="custom", description="", category="Custom") + + +class CustomErrorCodePlugin(Plugin): + def get_function_hook(self, fullname): + if fullname.endswith(".main"): + return self.emit_error + return None + + def emit_error(self, ctx): + ctx.api.fail("Custom error", ctx.context, code=CUSTOM_ERROR) + return AnyType(TypeOfAny.from_error) + + +def plugin(version): + return CustomErrorCodePlugin diff --git a/test-data/unit/plugins/fully_qualified_test_hook.py b/test-data/unit/plugins/fully_qualified_test_hook.py index df42d50be2653..529cf25a12153 100644 --- a/test-data/unit/plugins/fully_qualified_test_hook.py +++ b/test-data/unit/plugins/fully_qualified_test_hook.py @@ -6,7 +6,7 @@ def get_method_signature_hook(self, fullname): if 'FullyQualifiedTest' in fullname: assert fullname.startswith('__main__.') and not ' of ' in fullname, fullname return my_hook - + return None def my_hook(ctx: MethodSigContext) -> CallableType: diff --git a/test-data/unit/plugins/function_sig_hook.py b/test-data/unit/plugins/function_sig_hook.py index d83c7df262092..4d901b96716e7 100644 --- a/test-data/unit/plugins/function_sig_hook.py +++ b/test-data/unit/plugins/function_sig_hook.py @@ -1,5 +1,5 @@ -from mypy.plugin import CallableType, CheckerPluginInterface, FunctionSigContext, Plugin -from mypy.types import Instance, Type +from mypy.plugin import CallableType, FunctionSigContext, Plugin + class FunctionSigPlugin(Plugin): def get_function_signature_hook(self, fullname): @@ -7,20 +7,17 @@ def get_function_signature_hook(self, fullname): 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: + 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=[_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), + arg_types=[arg1_type], + ret_type=arg1_type, ) + def plugin(version): return FunctionSigPlugin diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index fbbaecbba2419..abc0f6a464a90 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) @@ -130,7 +130,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] @@ -268,7 +268,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] @@ -331,25 +333,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 @@ -372,18 +377,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(): @@ -432,7 +436,6 @@ True False [case testOverlappingOperatorMethods] - class X: pass class A: def __add__(self, x) -> int: @@ -442,11 +445,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 @@ -831,6 +834,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 @@ -856,11 +860,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" +_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[]]"; expected "defaultdict[int, List[]]" +_program.py:24: error: Invalid index type "str" for "MyDDict[Dict[, ]]"; expected type "int" [case testNoSubcriptionOfStdlibCollections] # flags: --python-version 3.6 @@ -963,10 +967,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] @@ -999,9 +1003,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: @@ -1014,11 +1022,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 @@ -1026,9 +1034,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: @@ -1041,11 +1053,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 @@ -1082,7 +1094,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}) @@ -1094,14 +1105,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 @@ -1140,10 +1151,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] @@ -1348,7 +1359,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 @@ -1371,7 +1382,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) @@ -1430,7 +1441,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" @@ -1878,6 +1889,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 @@ -1924,3 +1952,172 @@ _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[, ]", variable has type "MappingProxyType[str, Any]") + +[case testTypeVarTuple] +# flags: --enable-incomplete-feature=TypeVarTuple --enable-incomplete-feature=Unpack --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] +# flags: --enable-incomplete-feature=TypeVarTuple --enable-incomplete-feature=Unpack +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]" diff --git a/test-data/unit/ref-info.test b/test-data/unit/ref-info.test new file mode 100644 index 0000000000000..05426130d2726 --- /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/semanal-basic.test b/test-data/unit/semanal-basic.test index 20443517e03ec..169769f06a00b 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 86f8b8656fb61..951791e234901 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 a4ed905dcb9f5..a098dd8791d48 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 @@ -490,8 +490,9 @@ main:2: error: Can use starred expression only as assignment target [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] @@ -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 @@ -1275,8 +1275,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 +1291,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,7 +1454,7 @@ 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] @@ -1465,8 +1466,9 @@ 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 diff --git a/test-data/unit/semanal-expressions.test b/test-data/unit/semanal-expressions.test index fa07e533a842a..4c9baf6b1b75a 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 1cde1a794dc2f..cc2307b97217f 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 bc381293161fc..d52dd953aea2c 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 df1d5679c892c..f396f799028fe 100644 --- a/test-data/unit/semanal-namedtuple.test +++ b/test-data/unit/semanal-namedtuple.test @@ -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 9418ac2912b2f..e96a3ca9d7770 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 013452068cf1c..c143805f4564d 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 c886080557b06..1622fd1f1ad4a 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 debc7ecdf722a..88d2341343508 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 494d701b758a6..71a5c6dd87b51 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 @@ -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)))))) @@ -1582,7 +1582,7 @@ MypyFile:1( 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 8e4285b7de2e6..b387aa840dc91 100644 --- a/test-data/unit/stubgen.test +++ b/test-data/unit/stubgen.test @@ -127,10 +127,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 @@ -319,6 +361,62 @@ class A: def f(self, x) -> None: ... def h(self) -> None: ... +[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 @@ -585,8 +683,9 @@ class A: def _bar(cls) -> None: ... [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 +694,19 @@ class X(NamedTuple): a: Incomplete b: Incomplete +class Y(NamedTuple): + a: int + b: 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 +746,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 +761,10 @@ class X(NamedTuple): a: Incomplete b: Incomplete +class Y(NamedTuple): + a: int + b: str + def g() -> None: ... [case testNamedtupleBaseClass] @@ -672,10 +783,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 +807,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 +930,28 @@ 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 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(): @@ -873,11 +1078,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 +1099,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 +1302,9 @@ def h1(): def h2(): yield return "abc" +def h3(): + yield + return None def all(): x = yield 123 return "abc" @@ -1054,6 +1316,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 +1345,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 @@ -1780,6 +2106,36 @@ 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 A: + @property + @abc.abstractmethod + def x(self): ... + [case testAbstractProperty1_semanal] import other import abc @@ -2305,11 +2661,11 @@ __version__ = '' [out] [case testAttrsClass_semanal] -import attr +import attrs -@attr.s +@attrs.define class C: - x = attr.ib() + x = attrs.field() [out] from _typeshed import Incomplete @@ -2691,6 +3047,27 @@ def g(x: int, y: int) -> int: ... @te.overload def g(x: t.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 @@ -2738,13 +3115,17 @@ def func(*, non_default_kwarg: bool, default_kwarg: bool = True): ... def func(*, non_default_kwarg: bool, default_kwarg: bool = ...): ... [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 testKnownMagicMethodsReturnTypes] class Some: @@ -2793,3 +3174,206 @@ 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 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): ... diff --git a/test-data/unit/typexport-basic.test b/test-data/unit/typexport-basic.test index cd4071eb14ee4..0dcd0098f1776 100644 --- a/test-data/unit/typexport-basic.test +++ b/test-data/unit/typexport-basic.test @@ -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 -- ----------------- diff --git a/test-requirements.txt b/test-requirements.txt index aec11e87e96f6..5340973a4de13 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,20 +1,16 @@ -r mypy-requirements.txt -r build-requirements.txt attrs>=18.0 -black==22.12.0 # must match version in .pre-commit-config.yaml +black==23.3.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' +# 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.4.0 psutil>=4.0 -# pytest 6.2.3 does not support Python 3.10 -pytest>=6.2.4 +pytest>=7.4.0 pytest-xdist>=1.34.0 -pytest-forked>=1.3.0,<2.0.0 pytest-cov>=2.10.0 -py>=1.5.2 +ruff==0.0.272 # must match version in .pre-commit-config.yaml setuptools>=65.5.1 -six -tomli>=1.1.0 +tomli>=1.1.0 # needed even on py311+ so the self check passes with --python-version 3.7 diff --git a/tox.ini b/tox.ini index 443f05dc8bcfa..b5314114570b5 100644 --- a/tox.ini +++ b/tox.ini @@ -6,9 +6,9 @@ envlist = py38, py39, py310, + docs, lint, type, - docs, isolated_build = true [testenv] @@ -21,12 +21,30 @@ 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)' + +[testenv:docs] +description = invoke sphinx-build to build the HTML docs +passenv = + VERIFY_MYPY_ERROR_CODES +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: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 @@ -37,20 +55,3 @@ passenv = 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)'