Skip to content

Commit d92d24a

Browse files
authored
Merge branch 'master' into implement-lru-cache-support
2 parents d9466f0 + 6fcb8f0 commit d92d24a

487 files changed

Lines changed: 37941 additions & 6125 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test.yml

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ jobs:
9494
# This is broken. See
9595
# - https://github.com/python/mypy/issues/17819
9696
# - https://github.com/python/mypy/pull/17822
97-
# - name: mypyc runtime tests with py38-debug-build-ubuntu
98-
# python: '3.9.21'
97+
# - name: mypyc runtime tests with py310-debug-build-ubuntu
98+
# python: '3.10'
9999
# os: ubuntu-latest
100100
# toxenv: py
101101
# tox_extra_args: "-n 4 mypyc/test/test_run.py mypyc/test/test_external.py"
@@ -106,12 +106,20 @@ jobs:
106106
os: ubuntu-24.04-arm
107107
toxenv: py
108108
tox_extra_args: "-n 4 --mypy-num-workers=4 mypy/test/testcheck.py"
109+
dev_ast_serialize: true
109110
- name: Parallel tests with py314-ubuntu, mypyc-compiled
110111
python: '3.14'
111112
os: ubuntu-24.04-arm
112113
toxenv: py
113114
tox_extra_args: "-n 4 --mypy-num-workers=4 mypy/test/testcheck.py"
114115
test_mypyc: true
116+
dev_ast_serialize: true
117+
# Skip Windows tests until we publish ast_serialize wheels on PyPI.
118+
# - name: Parallel tests with py314-windows-64, interpreted
119+
# python: '3.14'
120+
# os: windows-latest
121+
# toxenv: py
122+
# tox_extra_args: "-n 2 --mypy-num-workers=2 mypy/test/testcheck.py -k 'incremental or modules or classes'"
115123

116124
- name: Type check our own code (py310-ubuntu)
117125
python: '3.10'
@@ -134,6 +142,7 @@ jobs:
134142
timeout-minutes: 60
135143
env:
136144
TOX_SKIP_MISSING_INTERPRETERS: False
145+
VIRTUALENV_SYSTEM_SITE_PACKAGES: ${{ matrix.dev_ast_serialize && 1 || 0 }}
137146
# Rich (pip) -- Disable color for windows + pytest
138147
FORCE_COLOR: ${{ !(startsWith(matrix.os, 'windows-') && startsWith(matrix.toxenv, 'py')) && 1 || 0 }}
139148
# Tox
@@ -152,24 +161,15 @@ jobs:
152161
with:
153162
persist-credentials: false
154163

155-
- name: Debug build
156-
if: ${{ matrix.debug_build }}
157-
run: |
158-
PYTHONVERSION=${{ matrix.python }}
159-
PYTHONDIR=~/python-debug/python-$PYTHONVERSION
160-
VENV=$PYTHONDIR/env
161-
./misc/build-debug-python.sh $PYTHONVERSION $PYTHONDIR $VENV
162-
# TODO: does this do anything? env vars aren't passed to the next step right
163-
source $VENV/bin/activate
164164
- name: Latest dev build
165165
if: ${{ endsWith(matrix.python, '-dev') }}
166166
run: |
167167
git clone --depth 1 https://github.com/python/cpython.git /tmp/cpython --branch $( echo ${{ matrix.python }} | sed 's/-dev//' )
168168
cd /tmp/cpython
169169
echo git rev-parse HEAD; git rev-parse HEAD
170170
git show --no-patch
171-
sudo apt-get update
172-
sudo apt-get install -y --no-install-recommends \
171+
sudo apt-get update -q
172+
sudo apt-get install -q -y --no-install-recommends \
173173
build-essential gdb lcov libbz2-dev libffi-dev libgdbm-dev liblzma-dev libncurses5-dev \
174174
libreadline6-dev libsqlite3-dev libssl-dev lzma lzma-dev tk-dev uuid-dev zlib1g-dev
175175
./configure --prefix=/opt/pythondev
@@ -178,6 +178,23 @@ jobs:
178178
sudo ln -s /opt/pythondev/bin/python3 /opt/pythondev/bin/python
179179
sudo ln -s /opt/pythondev/bin/pip3 /opt/pythondev/bin/pip
180180
echo "/opt/pythondev/bin" >> $GITHUB_PATH
181+
- name: Debug build
182+
if: ${{ matrix.debug_build }}
183+
run: |
184+
git clone --depth 1 https://github.com/python/cpython.git /tmp/cpython --branch ${{ matrix.python }}
185+
cd /tmp/cpython
186+
echo git rev-parse HEAD; git rev-parse HEAD
187+
git show --no-patch
188+
sudo apt-get update -q
189+
sudo apt-get install -q -y --no-install-recommends \
190+
build-essential gdb lcov libbz2-dev libffi-dev libgdbm-dev liblzma-dev libncurses5-dev \
191+
libreadline6-dev libsqlite3-dev libssl-dev lzma lzma-dev tk-dev uuid-dev zlib1g-dev
192+
./configure CFLAGS="-DPy_DEBUG -DPy_TRACE_REFS -DPYMALLOC_DEBUG" --with-pydebug -with-trace-refs --prefix=/opt/pythondev
193+
make -j$(nproc)
194+
sudo make install
195+
sudo ln -s /opt/pythondev/bin/python3 /opt/pythondev/bin/python
196+
sudo ln -s /opt/pythondev/bin/pip3 /opt/pythondev/bin/pip
197+
echo "/opt/pythondev/bin" >> $GITHUB_PATH
181198
- uses: actions/setup-python@v5
182199
if: ${{ !(matrix.debug_build || endsWith(matrix.python, '-dev')) }}
183200
with:
@@ -200,6 +217,30 @@ jobs:
200217
pip install -r test-requirements.txt
201218
CC=clang MYPYC_OPT_LEVEL=0 MYPY_USE_MYPYC=1 pip install -e .
202219
220+
# To speed-up process until ast_serialize is on PyPI.
221+
- name: Checkout pinned ast_serialize
222+
if: ${{ matrix.dev_ast_serialize }}
223+
uses: actions/checkout@v4
224+
with:
225+
persist-credentials: false
226+
repository: mypyc/ast_serialize
227+
ref: da5a16cf268dbec63ed6b2e6b715470576e2d1a6
228+
path: ast_serialize
229+
230+
- if: ${{ matrix.dev_ast_serialize }}
231+
uses: PyO3/maturin-action@v1
232+
with:
233+
target: aarch64
234+
args: --release --out dist
235+
working-directory: ast_serialize
236+
237+
- if: ${{ matrix.dev_ast_serialize }}
238+
run: |
239+
pip install ast_serialize/dist/ast_serialize-0.1.0-cp39-abi3-manylinux_2_34_aarch64.whl
240+
echo 'no way' > test_ast_serialize.py
241+
python3 -c 'import ast_serialize; print(ast_serialize.parse("test_ast_serialize.py")[1])'
242+
rm test_ast_serialize.py
243+
203244
- name: Setup tox environment
204245
run: |
205246
tox run -e ${{ matrix.toxenv }} --notest

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ repos:
66
- id: trailing-whitespace
77
- id: end-of-file-fixer
88
- repo: https://github.com/psf/black-pre-commit-mirror
9-
rev: 25.9.0
9+
rev: 26.1.0
1010
hooks:
1111
- id: black
1212
exclude: '^(test-data/)'

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ Support for this will be dropped in the first half of 2026!
1111

1212
Contributed by Marc Mueller (PR [20156](https://github.com/python/mypy/pull/20156)).
1313

14+
### Mypyc Accelerated Mypy Wheels for ARM Windows and Free Threading
15+
16+
For best performance, mypy can be compiled to C extension modules using mypyc. This makes
17+
mypy 3-5x faster than when interpreted with pure Python. We now build and upload mypyc
18+
accelerated mypy wheels for `win_arm64` and `cp314t-...` to PyPI, making it easy for Windows
19+
users on ARM and those using the free theading builds for Python 3.14 to realise this speedup
20+
-- just `pip install` the latest mypy.
21+
22+
Contributed by Marc Mueller
23+
(PR [mypy_mypyc-wheels#106](https://github.com/mypyc/mypy_mypyc-wheels/pull/106),
24+
PR [mypy_mypyc-wheels#110](https://github.com/mypyc/mypy_mypyc-wheels/pull/110)).
25+
1426
### Removed flags `--force-uppercase-builtins` and `--force-union-syntax`
1527

1628
The `--force-uppercase-builtins` flag was deprecated and has been a no-op since mypy 1.17.0.

docs/source/command_line.rst

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -537,13 +537,15 @@ potentially problematic or redundant in some way.
537537
print(x + "bad")
538538
539539
To help prevent mypy from generating spurious warnings, the "Statement is
540-
unreachable" warning will be silenced in exactly two cases:
540+
unreachable" warning will be silenced in exactly three cases:
541541

542542
1. When the unreachable statement is a ``raise`` statement, is an
543543
``assert False`` statement, or calls a function that has the :py:data:`~typing.NoReturn`
544544
return type hint. In other words, when the unreachable statement
545545
throws an error or terminates the program in some way.
546-
2. When the unreachable statement was *intentionally* marked as unreachable
546+
2. When the unreachable statement is ``return NotImplemented``. This
547+
is allowed by mypy due to its use in operator overloading.
548+
3. When the unreachable statement was *intentionally* marked as unreachable
547549
using :ref:`version_and_platform_checks`.
548550

549551
.. note::
@@ -596,8 +598,8 @@ of the above sections.
596598
.. option:: --allow-redefinition-new
597599

598600
By default, mypy won't allow a variable to be redefined with an
599-
unrelated type. This *experimental* flag enables the redefinition of
600-
unannotated variables with an arbitrary type. You will also need to enable
601+
unrelated type. This flag enables the redefinition of unannotated
602+
variables with an arbitrary type. You will also need to enable
601603
:option:`--local-partial-types <mypy --local-partial-types>`.
602604
Example:
603605

@@ -629,12 +631,31 @@ of the above sections.
629631
# Type of "x" is "str" here.
630632
...
631633
634+
Function arguments are special, changing their type within function body
635+
is allowed even if they are annotated, but that annotation is used to infer
636+
types of r.h.s. of all subsequent assignments. Such middle-ground semantics
637+
provides good balance for majority of common use cases. For example:
638+
639+
.. code-block:: python
640+
641+
def process(values: list[float]) -> None:
642+
if not values:
643+
values = [0, 0, 0]
644+
reveal_type(values) # Revealed type is list[float]
645+
632646
Note: We are planning to turn this flag on by default in a future mypy
633647
release, along with :option:`--local-partial-types <mypy --local-partial-types>`.
634648
The feature is still experimental, and the semantics may still change.
635649

636650
.. option:: --allow-redefinition
637651

652+
This is an alias to :option:`--allow-redefinition-old <mypy --allow-redefinition-old>`.
653+
In mypy v2.0 this will point to
654+
:option:`--allow-redefinition-new <mypy --allow-redefinition-new>`, and will
655+
eventually became the default.
656+
657+
.. option:: --allow-redefinition-old
658+
638659
This is an older variant of
639660
:option:`--allow-redefinition-new <mypy --allow-redefinition-new>`.
640661
This flag enables redefinition of a variable with an
@@ -687,7 +708,7 @@ of the above sections.
687708
reveal_type(Foo().bar) # 'int | None' without --local-partial-types
688709
689710
Note: this option is always implicitly enabled in mypy daemon and will become
690-
enabled by default for mypy in a future release.
711+
enabled by default in mypy v2.0 release.
691712

692713
.. option:: --no-implicit-reexport
693714

docs/source/common_issues.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ error:
148148
The second line is now fine, since the ignore comment causes the name
149149
``frobnicate`` to get an implicit ``Any`` type.
150150

151+
The type ignore comment must be at the start of the comments on a line.
152+
This type ignore will not take effect:
153+
154+
.. code-block:: python
155+
156+
import frobnicate #example other comment # type: ignore
157+
frobnicate.start()
158+
151159
.. note::
152160

153161
You can use the form ``# type: ignore[<code>]`` to only ignore

docs/source/config_file.rst

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -719,8 +719,8 @@ section of the command line docs.
719719
:default: False
720720

721721
By default, mypy won't allow a variable to be redefined with an
722-
unrelated type. This *experimental* flag enables the redefinition of
723-
unannotated variables with an arbitrary type. You will also need to enable
722+
unrelated type. This flag enables the redefinition of unannotated
723+
variables with an arbitrary type. You will also need to enable
724724
:confval:`local_partial_types`.
725725
Example:
726726

@@ -748,10 +748,22 @@ section of the command line docs.
748748
# Type of "x" is "str" here.
749749
...
750750
751+
Function arguments are special, changing their type within function body
752+
is allowed even if they are annotated, but that annotation is used to infer
753+
types of r.h.s. of all subsequent assignments. Such middle-ground semantics
754+
provides good balance for majority of common use cases. For example:
755+
756+
.. code-block:: python
757+
758+
def process(values: list[float]) -> None:
759+
if not values:
760+
values = [0, 0, 0]
761+
reveal_type(values) # Revealed type is list[float]
762+
751763
Note: We are planning to turn this flag on by default in a future mypy
752764
release, along with :confval:`local_partial_types`.
753765

754-
.. confval:: allow_redefinition
766+
.. confval:: allow_redefinition_old
755767

756768
:type: boolean
757769
:default: False
@@ -777,14 +789,22 @@ section of the command line docs.
777789
items = "100" # valid, items now has type str
778790
items = int(items) # valid, items now has type int
779791
792+
.. confval:: allow_redefinition
793+
794+
:type: boolean
795+
:default: False
796+
797+
An alias to :confval:`allow_redefinition_old`, in mypy v2.0 this will point to
798+
:confval:`allow_redefinition_new`, and will eventually became the default.
799+
780800
.. confval:: local_partial_types
781801

782802
:type: boolean
783803
:default: False
784804

785805
Disallows inferring variable type for ``None`` from two assignments in different scopes.
786806
This is always implicitly enabled when using the :ref:`mypy daemon <mypy_daemon>`.
787-
This will be enabled by default in a future mypy release.
807+
This will be enabled by default in mypy v2.0 release.
788808

789809
.. confval:: disable_error_code
790810

docs/source/error_code_list2.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Example:
3232
3333
# mypy: disallow-any-generics
3434
35-
# Error: Missing type parameters for generic type "list" [type-arg]
35+
# Error: Missing type arguments for generic type "list" [type-arg]
3636
def remove_dups(items: list) -> list:
3737
...
3838

docs/source/stubs.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ For example:
126126
"""Some docstring"""
127127
pass
128128
129-
# Error: Incompatible default for argument "foo" (default has
130-
# type "ellipsis", argument has type "list[str]")
129+
# Error: Incompatible default for parameter "foo" (default has
130+
# type "ellipsis", parameter has type "list[str]")
131131
def not_ok(self, foo: list[str] = ...) -> None:
132132
print(foo)

docs/source/type_inference_and_annotations.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ on the declared type of ``arg`` in ``foo``:
159159
.. code-block:: python
160160
161161
def foo(arg: list[int]) -> None:
162-
print('Items:', ''.join(str(a) for a in arg))
162+
print('Items:', ', '.join(str(a) for a in arg))
163163
164164
foo([]) # OK
165165
@@ -170,7 +170,7 @@ in the following statement:
170170
.. code-block:: python
171171
172172
def foo(arg: list[int]) -> None:
173-
print('Items:', ', '.join(arg))
173+
print('Items:', ', '.join(str(a) for a in arg))
174174
175175
a = [] # Error: Need type annotation for "a"
176176
foo(a)

misc/apply-cache-diff.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
44
With some infrastructure, this can allow for distributing small cache diffs to users in
55
many cases instead of full cache artifacts.
6+
7+
Use diff-cache.py to generate a cache diff.
68
"""
79

810
from __future__ import annotations
@@ -13,6 +15,10 @@
1315

1416
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
1517

18+
from librt import base64
19+
from librt.internal import ReadBuffer
20+
21+
from mypy.cache import CacheMeta
1622
from mypy.metastore import FilesystemMetadataStore, MetadataStore, SqliteMetadataStore
1723
from mypy.util import json_dumps, json_loads
1824

@@ -35,10 +41,19 @@ def apply_diff(cache_dir: str, diff_file: str, sqlite: bool = False) -> None:
3541
if data is None:
3642
cache.remove(file)
3743
else:
38-
cache.write(file, data)
39-
if file.endswith(".meta.json") and "@deps" not in file:
40-
meta = json_loads(data)
41-
old_deps["snapshot"][meta["id"]] = meta["hash"]
44+
if file.endswith(".ff"):
45+
data_bytes = base64.b64decode(data)
46+
else:
47+
data_bytes = data.encode() if isinstance(data, str) else data
48+
cache.write(file, data_bytes)
49+
if file.endswith(".meta.ff") and "@deps" not in file:
50+
buf = ReadBuffer(data_bytes[2:])
51+
meta = CacheMeta.read(buf, data_file="")
52+
assert meta is not None
53+
old_deps["snapshot"][meta.id] = meta.hash
54+
elif file.endswith(".meta.json") and "@deps" not in file:
55+
meta_dict = json_loads(data_bytes)
56+
old_deps["snapshot"][meta_dict["id"]] = meta_dict["hash"]
4257

4358
cache.write("@deps.meta.json", json_dumps(old_deps))
4459

0 commit comments

Comments
 (0)