From 13369cb25fe450f755f63e59156b86df84c08b3d Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 23 Nov 2025 07:10:10 +0000 Subject: [PATCH 01/18] [mypyc] Fix crash on super in generator (#20291) This is another problem caused by https://github.com/python/mypy/pull/19398 and a missing part of https://github.com/python/mypy/pull/20254 cc @JukkaL @hauntsaninja there is a small chance this may be related to the problems with black. --- mypyc/irbuild/expression.py | 4 ++-- mypyc/test-data/run-classes.test | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index 86cc2c7fb145..2ed347ca1e79 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -296,8 +296,8 @@ def transform_super_expr(builder: IRBuilder, o: SuperExpr) -> Value: # Grab first argument vself: Value = next(iter_env) if builder.fn_info.is_generator: - # grab sixth argument (see comment in translate_super_method_call) - self_targ = list(builder.symtables[-1].values())[6] + # grab seventh argument (see comment in translate_super_method_call) + self_targ = list(builder.symtables[-1].values())[7] vself = builder.read(self_targ, builder.fn_info.fitem.line) elif not ir.is_ext_class: vself = next(iter_env) # second argument is self if non_extension class diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index 2c2eac505797..da72fe59f456 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -322,6 +322,17 @@ if sys.version_info[:2] > (3, 5): assert TestEnum.b.name == 'b' assert TestEnum.b.value == 2 +[case testRunSuperYieldFromDict] +from typing import Any, Iterator + +class DictSubclass(dict): + def items(self) -> Iterator[Any]: + yield 1 + yield from super().items() + +def test_sub_dict() -> None: + assert list(DictSubclass().items()) == [1] + [case testGetAttribute] class C: x: int From 1b94fbb9fbc581de7e057d71e9892e3acbf9a7d3 Mon Sep 17 00:00:00 2001 From: Piotr Sawicki Date: Thu, 27 Nov 2025 19:21:10 +0100 Subject: [PATCH 02/18] [mypyc] Fix vtable pointer with inherited dunder new (#20302) Fixes an issue where a subclass would have its vtable pointer set to the base class' vtable when there is a `__new__` method defined in the base class. This resulted in the subclass constructor calling the setup function of the base class because mypyc transforms `object.__new__` into the setup function. The fix is to store the pointers to the setup functions in `tp_methods` of type objects and look them up dynamically when instantiating new objects. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- mypyc/codegen/emitclass.py | 13 ++++++- mypyc/irbuild/specialize.py | 11 +++++- mypyc/lib-rt/CPy.h | 2 + mypyc/lib-rt/generic_ops.c | 20 ++++++++++ mypyc/primitives/generic_ops.py | 7 ++++ mypyc/test-data/irbuild-classes.test | 28 ++++++++++++++ mypyc/test-data/run-classes.test | 55 ++++++++++++++++++++++++++++ 7 files changed, 132 insertions(+), 4 deletions(-) diff --git a/mypyc/codegen/emitclass.py b/mypyc/codegen/emitclass.py index d64940084f12..e190d45a2e93 100644 --- a/mypyc/codegen/emitclass.py +++ b/mypyc/codegen/emitclass.py @@ -359,7 +359,7 @@ def emit_line() -> None: if cl.is_trait: generate_new_for_trait(cl, new_name, emitter) - generate_methods_table(cl, methods_name, emitter) + generate_methods_table(cl, methods_name, setup_name if generate_full else None, emitter) emit_line() flags = ["Py_TPFLAGS_DEFAULT", "Py_TPFLAGS_HEAPTYPE", "Py_TPFLAGS_BASETYPE"] @@ -960,8 +960,17 @@ def generate_finalize_for_class( emitter.emit_line("}") -def generate_methods_table(cl: ClassIR, name: str, emitter: Emitter) -> None: +def generate_methods_table( + cl: ClassIR, name: str, setup_name: str | None, emitter: Emitter +) -> None: emitter.emit_line(f"static PyMethodDef {name}[] = {{") + if setup_name: + # Store pointer to the setup function so it can be resolved dynamically + # in case of instance creation in __new__. + # CPy_SetupObject expects this method to be the first one in tp_methods. + emitter.emit_line( + f'{{"__internal_mypyc_setup", (PyCFunction){setup_name}, METH_O, NULL}},' + ) for fn in cl.methods.values(): if fn.decl.is_prop_setter or fn.decl.is_prop_getter or fn.internal: continue diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index e810f11bd079..b64f51043b13 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -99,7 +99,7 @@ isinstance_dict, ) from mypyc.primitives.float_ops import isinstance_float -from mypyc.primitives.generic_ops import generic_setattr +from mypyc.primitives.generic_ops import generic_setattr, setup_object from mypyc.primitives.int_ops import isinstance_int from mypyc.primitives.list_ops import isinstance_list, new_list_set_item_op from mypyc.primitives.misc_ops import isinstance_bool @@ -1103,7 +1103,14 @@ def translate_object_new(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> method_args = fn.fitem.arg_names if isinstance(typ_arg, NameExpr) and len(method_args) > 0 and method_args[0] == typ_arg.name: subtype = builder.accept(expr.args[0]) - return builder.add(Call(ir.setup, [subtype], expr.line)) + subs = ir.subclasses() + if subs is not None and len(subs) == 0: + return builder.add(Call(ir.setup, [subtype], expr.line)) + # Call a function that dynamically resolves the setup function of extension classes from the type object. + # This is necessary because the setup involves default attribute initialization and setting up + # the vtable which are specific to a given type and will not work if a subtype is created using + # the setup function of its base. + return builder.call_c(setup_object, [subtype], expr.line) return None diff --git a/mypyc/lib-rt/CPy.h b/mypyc/lib-rt/CPy.h index c79923f69e69..6d1e7502a7e7 100644 --- a/mypyc/lib-rt/CPy.h +++ b/mypyc/lib-rt/CPy.h @@ -958,6 +958,8 @@ static inline int CPyObject_GenericSetAttr(PyObject *self, PyObject *name, PyObj return _PyObject_GenericSetAttrWithDict(self, name, value, NULL); } +PyObject *CPy_SetupObject(PyObject *type); + #if CPY_3_11_FEATURES PyObject *CPy_GetName(PyObject *obj); #endif diff --git a/mypyc/lib-rt/generic_ops.c b/mypyc/lib-rt/generic_ops.c index 260cfec5b360..1e1e184bf290 100644 --- a/mypyc/lib-rt/generic_ops.c +++ b/mypyc/lib-rt/generic_ops.c @@ -62,3 +62,23 @@ PyObject *CPyObject_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end) { Py_DECREF(slice); return result; } + +typedef PyObject *(*SetupFunction)(PyObject *); + +PyObject *CPy_SetupObject(PyObject *type) { + PyTypeObject *tp = (PyTypeObject *)type; + PyMethodDef *def = NULL; + for(; tp; tp = tp->tp_base) { + def = tp->tp_methods; + if (!def || !def->ml_name) { + continue; + } + + if (!strcmp(def->ml_name, "__internal_mypyc_setup")) { + return ((SetupFunction)(void(*)(void))def->ml_meth)(type); + } + } + + PyErr_SetString(PyExc_RuntimeError, "Internal mypyc error: Unable to find object setup function"); + return NULL; +} diff --git a/mypyc/primitives/generic_ops.py b/mypyc/primitives/generic_ops.py index 16bd074396d2..1003fda8d9ae 100644 --- a/mypyc/primitives/generic_ops.py +++ b/mypyc/primitives/generic_ops.py @@ -417,3 +417,10 @@ c_function_name="CPyObject_GenericSetAttr", error_kind=ERR_NEG_INT, ) + +setup_object = custom_op( + arg_types=[object_rprimitive], + return_type=object_rprimitive, + c_function_name="CPy_SetupObject", + error_kind=ERR_MAGIC, +) diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index 0f8ec2b094f0..504e6234de24 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -1685,6 +1685,13 @@ class Test: obj.val = val return obj +class Test2: + def __new__(cls) -> Test2: + return super().__new__(cls) + +class Sub(Test2): + pass + def fn() -> Test: return Test.__new__(Test, 42) @@ -1719,6 +1726,13 @@ L0: obj = r0 obj.val = val; r1 = is_error return obj +def Test2.__new__(cls): + cls, r0 :: object + r1 :: __main__.Test2 +L0: + r0 = CPy_SetupObject(cls) + r1 = cast(__main__.Test2, r0) + return r1 def fn(): r0 :: object r1 :: __main__.Test @@ -1822,6 +1836,13 @@ class Test: obj.val = val return obj +class Test2: + def __new__(cls) -> Test2: + return object.__new__(cls) + +class Sub(Test2): + pass + def fn() -> Test: return Test.__new__(Test, 42) @@ -1874,6 +1895,13 @@ L0: obj = r0 obj.val = val; r1 = is_error return obj +def Test2.__new__(cls): + cls, r0 :: object + r1 :: __main__.Test2 +L0: + r0 = CPy_SetupObject(cls) + r1 = cast(__main__.Test2, r0) + return r1 def fn(): r0 :: object r1 :: __main__.Test diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index da72fe59f456..02a9934bac71 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -3859,6 +3859,7 @@ Add(1, 0)=1 [case testInheritedDunderNew] from __future__ import annotations from mypy_extensions import mypyc_attr +from testutil import assertRaises from typing_extensions import Self from m import interpreted_subclass @@ -3875,7 +3876,11 @@ class Base: def __init__(self, val: int) -> None: self.init_val = val + def method(self) -> int: + raise NotImplementedError + class Sub(Base): + def __new__(cls, val: int) -> Self: return super().__new__(cls, val + 1) @@ -3883,11 +3888,20 @@ class Sub(Base): super().__init__(val) self.init_val = self.init_val * 2 + def method(self) -> int: + return 0 + class SubWithoutNew(Base): + sub_only_str = "" + sub_only_int: int + def __init__(self, val: int) -> None: super().__init__(val) self.init_val = self.init_val * 2 + def method(self) -> int: + return 1 + class BaseWithoutInterpretedSubclasses: val: int @@ -3899,6 +3913,9 @@ class BaseWithoutInterpretedSubclasses: def __init__(self, val: int) -> None: self.init_val = val + def method(self) -> int: + raise NotImplementedError + class SubNoInterpreted(BaseWithoutInterpretedSubclasses): def __new__(cls, val: int) -> Self: return super().__new__(cls, val + 1) @@ -3907,48 +3924,68 @@ class SubNoInterpreted(BaseWithoutInterpretedSubclasses): super().__init__(val) self.init_val = self.init_val * 2 + def method(self) -> int: + return 0 + class SubNoInterpretedWithoutNew(BaseWithoutInterpretedSubclasses): def __init__(self, val: int) -> None: super().__init__(val) self.init_val = self.init_val * 2 + def method(self) -> int: + return 1 + def test_inherited_dunder_new() -> None: b = Base(42) assert type(b) == Base assert b.val == 43 assert b.init_val == 42 + with assertRaises(NotImplementedError): + b.method() s = Sub(42) assert type(s) == Sub assert s.val == 44 assert s.init_val == 84 + assert s.method() == 0 s2 = SubWithoutNew(42) assert type(s2) == SubWithoutNew assert s2.val == 43 assert s2.init_val == 84 + assert s2.method() == 1 + assert s2.sub_only_str == "" + with assertRaises(AttributeError): + s2.sub_only_int + s2.sub_only_int = 11 + assert s2.sub_only_int == 11 def test_inherited_dunder_new_without_interpreted_subclasses() -> None: b = BaseWithoutInterpretedSubclasses(42) assert type(b) == BaseWithoutInterpretedSubclasses assert b.val == 43 assert b.init_val == 42 + with assertRaises(NotImplementedError): + b.method() s = SubNoInterpreted(42) assert type(s) == SubNoInterpreted assert s.val == 44 assert s.init_val == 84 + assert s.method() == 0 s2 = SubNoInterpretedWithoutNew(42) assert type(s2) == SubNoInterpretedWithoutNew assert s2.val == 43 assert s2.init_val == 84 + assert s2.method() == 1 def test_interpreted_subclass() -> None: interpreted_subclass(Base) [file m.py] from __future__ import annotations +from testutil import assertRaises from typing_extensions import Self def interpreted_subclass(base) -> None: @@ -3956,6 +3993,8 @@ def interpreted_subclass(base) -> None: assert type(b) == base assert b.val == 43 assert b.init_val == 42 + with assertRaises(NotImplementedError): + b.method() class InterpretedSub(base): def __new__(cls, val: int) -> Self: @@ -3965,20 +4004,36 @@ def interpreted_subclass(base) -> None: super().__init__(val) self.init_val : int = self.init_val * 2 + def method(self) -> int: + return 3 + s = InterpretedSub(42) assert type(s) == InterpretedSub assert s.val == 44 assert s.init_val == 84 + assert s.method() == 3 class InterpretedSubWithoutNew(base): + sub_only_str = "" + sub_only_int: int + def __init__(self, val: int) -> None: super().__init__(val) self.init_val : int = self.init_val * 2 + def method(self) -> int: + return 4 + s2 = InterpretedSubWithoutNew(42) assert type(s2) == InterpretedSubWithoutNew assert s2.val == 43 assert s2.init_val == 84 + assert s2.method() == 4 + assert s2.sub_only_str == "" + with assertRaises(AttributeError): + s2.sub_only_int + s2.sub_only_int = 11 + assert s2.sub_only_int == 11 [typing fixtures/typing-full.pyi] From 1999a20e9898f673fa2f4c9a91790c075141ba71 Mon Sep 17 00:00:00 2001 From: "Michael R. Crusoe" <1330696+mr-c@users.noreply.github.com> Date: Fri, 28 Nov 2025 10:58:29 +0100 Subject: [PATCH 03/18] [mypyc] librt base64: use existing SIMD CPU dispatch by customizing build flags (#20253) Fixes the current SSE4.2 requirement added in https://github.com/python/mypy/commit/1b6ebb17b7fe64488a7b3c3b4b0187bb14fe331b / https://github.com/python/mypy/pull/20244 This PR fully enables the existing x86-64 CPU detection and dispatch code for SSSE3, SSE4.1, SSE4.2, AVX, and AVX2 in the base64 module. To use the existing CPU dispatch from the [upstream base64 code](https://github.com/aklomp/base64), one needs to compile the sources in each of the CPU specific codec directories with a specific compiler flag; alas this is difficult to do with setuptools, but I found a solution inspired by https://stackoverflow.com/a/68508804 Note that I did not enable the AVX512 path with this PR, as many intel CPUs that support AVX512 can come with a performance hit if AVX512 is sporadically used; the performance of the AVX512 (encoding) path need to be evaluated in the context of how mypyc uses base64 in various realistic scenarios. (There is no AVX512 accelerated decoding path in the upstream base64 codebase, it falls back to the avx2 decoder). If there are additional performance concerns, then I suggest benchmarking with the openmp feature of base64 turned on, for multi-core processing. --- mypy_self_check.ini | 2 +- mypyc/build.py | 17 ++++---- mypyc/build_setup.py | 62 +++++++++++++++++++++++++++ mypyc/common.py | 3 -- mypyc/lib-rt/base64/arch/avx/codec.c | 2 +- mypyc/lib-rt/base64/arch/avx2/codec.c | 12 +++--- mypyc/lib-rt/base64/config.h | 28 +++--------- mypyc/lib-rt/setup.py | 54 ++++++++++++++++++++--- setup.py | 1 + 9 files changed, 135 insertions(+), 46 deletions(-) create mode 100644 mypyc/build_setup.py diff --git a/mypy_self_check.ini b/mypy_self_check.ini index 0b49b3de862b..f4f8d2d0e08b 100644 --- a/mypy_self_check.ini +++ b/mypy_self_check.ini @@ -8,7 +8,7 @@ pretty = True always_false = MYPYC plugins = mypy.plugins.proper_plugin python_version = 3.9 -exclude = mypy/typeshed/|mypyc/test-data/|mypyc/lib-rt/ +exclude = mypy/typeshed/|mypyc/test-data/ enable_error_code = ignore-without-code,redundant-expr enable_incomplete_feature = PreciseTupleTypes show_error_code_links = True diff --git a/mypyc/build.py b/mypyc/build.py index 02f427c83426..69ef6c3bc435 100644 --- a/mypyc/build.py +++ b/mypyc/build.py @@ -28,6 +28,7 @@ from collections.abc import Iterable from typing import TYPE_CHECKING, Any, NamedTuple, NoReturn, Union, cast +import mypyc.build_setup # noqa: F401 from mypy.build import BuildSource from mypy.errors import CompileError from mypy.fscache import FileSystemCache @@ -36,7 +37,7 @@ from mypy.util import write_junit_xml from mypyc.annotate import generate_annotated_html from mypyc.codegen import emitmodule -from mypyc.common import IS_FREE_THREADED, RUNTIME_C_FILES, X86_64, shared_lib_name +from mypyc.common import IS_FREE_THREADED, RUNTIME_C_FILES, shared_lib_name from mypyc.errors import Errors from mypyc.ir.pprint import format_modules from mypyc.namegen import exported_name @@ -70,6 +71,13 @@ class ModDesc(NamedTuple): "base64/arch/neon64/codec.c", ], [ + "base64/arch/avx/enc_loop_asm.c", + "base64/arch/avx2/enc_loop.c", + "base64/arch/avx2/enc_loop_asm.c", + "base64/arch/avx2/enc_reshuffle.c", + "base64/arch/avx2/enc_translate.c", + "base64/arch/avx2/dec_loop.c", + "base64/arch/avx2/dec_reshuffle.c", "base64/arch/generic/32/enc_loop.c", "base64/arch/generic/64/enc_loop.c", "base64/arch/generic/32/dec_loop.c", @@ -661,9 +669,6 @@ def mypycify( # See https://github.com/mypyc/mypyc/issues/956 "-Wno-cpp", ] - if X86_64: - # Enable SIMD extensions. All CPUs released since ~2010 support SSE4.2. - cflags.append("-msse4.2") if log_trace: cflags.append("-DMYPYC_LOG_TRACE") if experimental_features: @@ -692,10 +697,6 @@ def mypycify( # that we actually get the compilation speed and memory # use wins that multi-file mode is intended for. cflags += ["/GL-", "/wd9025"] # warning about overriding /GL - if X86_64: - # Enable SIMD extensions. All CPUs released since ~2010 support SSE4.2. - # Also Windows 11 requires SSE4.2 since 24H2. - cflags.append("/arch:SSE4.2") if log_trace: cflags.append("/DMYPYC_LOG_TRACE") if experimental_features: diff --git a/mypyc/build_setup.py b/mypyc/build_setup.py new file mode 100644 index 000000000000..a3e7a669abee --- /dev/null +++ b/mypyc/build_setup.py @@ -0,0 +1,62 @@ +import platform +import sys + +try: + # Import setuptools so that it monkey-patch overrides distutils + import setuptools # noqa: F401 +except ImportError: + pass + +if sys.version_info >= (3, 12): + # From setuptools' monkeypatch + from distutils import ccompiler # type: ignore[import-not-found] +else: + from distutils import ccompiler + +EXTRA_FLAGS_PER_COMPILER_TYPE_PER_PATH_COMPONENT = { + "unix": { + "base64/arch/ssse3": ["-mssse3"], + "base64/arch/sse41": ["-msse4.1"], + "base64/arch/sse42": ["-msse4.2"], + "base64/arch/avx2": ["-mavx2"], + "base64/arch/avx": ["-mavx"], + }, + "msvc": { + "base64/arch/sse42": ["/arch:SSE4.2"], + "base64/arch/avx2": ["/arch:AVX2"], + "base64/arch/avx": ["/arch:AVX"], + }, +} + +ccompiler.CCompiler.__spawn = ccompiler.CCompiler.spawn # type: ignore[attr-defined] +X86_64 = platform.machine() in ("x86_64", "AMD64", "amd64") + + +def spawn(self, cmd, **kwargs) -> None: # type: ignore[no-untyped-def] + compiler_type: str = self.compiler_type + extra_options = EXTRA_FLAGS_PER_COMPILER_TYPE_PER_PATH_COMPONENT[compiler_type] + new_cmd = list(cmd) + if X86_64 and extra_options is not None: + # filenames are closer to the end of command line + for argument in reversed(new_cmd): + # Check if the matching argument contains a source filename. + if not str(argument).endswith(".c"): + continue + + for path in extra_options.keys(): + if path in str(argument): + if compiler_type == "bcpp": + compiler = new_cmd.pop() + # Borland accepts a source file name at the end, + # insert the options before it + new_cmd.extend(extra_options[path]) + new_cmd.append(compiler) + else: + new_cmd.extend(extra_options[path]) + + # path component is found, no need to search any further + break + self.__spawn(new_cmd, **kwargs) + + +ccompiler.CCompiler.spawn = spawn # type: ignore[method-assign] diff --git a/mypyc/common.py b/mypyc/common.py index 98f8a89f6fcb..2de63c09bb2c 100644 --- a/mypyc/common.py +++ b/mypyc/common.py @@ -1,6 +1,5 @@ from __future__ import annotations -import platform import sys import sysconfig from typing import Any, Final @@ -45,8 +44,6 @@ IS_32_BIT_PLATFORM: Final = int(SIZEOF_SIZE_T) == 4 -X86_64: Final = platform.machine() in ("x86_64", "AMD64", "amd64") - PLATFORM_SIZE = 4 if IS_32_BIT_PLATFORM else 8 # Maximum value for a short tagged integer. diff --git a/mypyc/lib-rt/base64/arch/avx/codec.c b/mypyc/lib-rt/base64/arch/avx/codec.c index 8e2ef5c2e724..7a64a94be2af 100644 --- a/mypyc/lib-rt/base64/arch/avx/codec.c +++ b/mypyc/lib-rt/base64/arch/avx/codec.c @@ -24,7 +24,7 @@ #include "../ssse3/dec_loop.c" #if BASE64_AVX_USE_ASM -# include "enc_loop_asm.c" +# include "./enc_loop_asm.c" #else # include "../ssse3/enc_translate.c" # include "../ssse3/enc_reshuffle.c" diff --git a/mypyc/lib-rt/base64/arch/avx2/codec.c b/mypyc/lib-rt/base64/arch/avx2/codec.c index fe9200296914..a54385bf89be 100644 --- a/mypyc/lib-rt/base64/arch/avx2/codec.c +++ b/mypyc/lib-rt/base64/arch/avx2/codec.c @@ -20,15 +20,15 @@ # endif #endif -#include "dec_reshuffle.c" -#include "dec_loop.c" +#include "./dec_reshuffle.c" +#include "./dec_loop.c" #if BASE64_AVX2_USE_ASM -# include "enc_loop_asm.c" +# include "./enc_loop_asm.c" #else -# include "enc_translate.c" -# include "enc_reshuffle.c" -# include "enc_loop.c" +# include "./enc_translate.c" +# include "./enc_reshuffle.c" +# include "./enc_loop.c" #endif #endif // HAVE_AVX2 diff --git a/mypyc/lib-rt/base64/config.h b/mypyc/lib-rt/base64/config.h index b5e47fb04e75..467a722c2f11 100644 --- a/mypyc/lib-rt/base64/config.h +++ b/mypyc/lib-rt/base64/config.h @@ -1,29 +1,15 @@ #ifndef BASE64_CONFIG_H #define BASE64_CONFIG_H -#define BASE64_WITH_SSSE3 0 -#define HAVE_SSSE3 BASE64_WITH_SSSE3 - -#define BASE64_WITH_SSE41 0 -#define HAVE_SSE41 BASE64_WITH_SSE41 - -#if defined(__x86_64__) || defined(_M_X64) -#define BASE64_WITH_SSE42 1 -#else -#define BASE64_WITH_SSE42 0 +#if !defined(__APPLE__) && ((defined(__x86_64__) && defined(__LP64__)) || defined(_M_X64)) + #define HAVE_SSSE3 1 + #define HAVE_SSE41 1 + #define HAVE_SSE42 1 + #define HAVE_AVX 1 + #define HAVE_AVX2 1 + #define HAVE_AVX512 0 #endif -#define HAVE_SSE42 BASE64_WITH_SSE42 - -#define BASE64_WITH_AVX 0 -#define HAVE_AVX BASE64_WITH_AVX - -#define BASE64_WITH_AVX2 0 -#define HAVE_AVX2 BASE64_WITH_AVX2 - -#define BASE64_WITH_AVX512 0 -#define HAVE_AVX512 BASE64_WITH_AVX512 - #define BASE64_WITH_NEON32 0 #define HAVE_NEON32 BASE64_WITH_NEON32 diff --git a/mypyc/lib-rt/setup.py b/mypyc/lib-rt/setup.py index 6a56c65306ae..72dfc15d8588 100644 --- a/mypyc/lib-rt/setup.py +++ b/mypyc/lib-rt/setup.py @@ -25,9 +25,55 @@ "pythonsupport.c", ] +EXTRA_FLAGS_PER_COMPILER_TYPE_PER_PATH_COMPONENT = { + "unix": { + "base64/arch/ssse3": ["-mssse3"], + "base64/arch/sse41": ["-msse4.1"], + "base64/arch/sse42": ["-msse4.2"], + "base64/arch/avx2": ["-mavx2"], + "base64/arch/avx": ["-mavx"], + }, + "msvc": { + "base64/arch/sse42": ["/arch:SSE4.2"], + "base64/arch/avx2": ["/arch:AVX2"], + "base64/arch/avx": ["/arch:AVX"], + }, +} + +ccompiler.CCompiler.__spawn = ccompiler.CCompiler.spawn # type: ignore[attr-defined] X86_64 = platform.machine() in ("x86_64", "AMD64", "amd64") +def spawn(self, cmd, **kwargs) -> None: # type: ignore[no-untyped-def] + compiler_type: str = self.compiler_type + extra_options = EXTRA_FLAGS_PER_COMPILER_TYPE_PER_PATH_COMPONENT[compiler_type] + new_cmd = list(cmd) + if X86_64 and extra_options is not None: + # filenames are closer to the end of command line + for argument in reversed(new_cmd): + # Check if the matching argument contains a source filename. + if not str(argument).endswith(".c"): + continue + + for path in extra_options.keys(): + if path in str(argument): + if compiler_type == "bcpp": + compiler = new_cmd.pop() + # Borland accepts a source file name at the end, + # insert the options before it + new_cmd.extend(extra_options[path]) + new_cmd.append(compiler) + else: + new_cmd.extend(extra_options[path]) + + # path component is found, no need to search any further + break + self.__spawn(new_cmd, **kwargs) + + +ccompiler.CCompiler.spawn = spawn # type: ignore[method-assign] + + class BuildExtGtest(build_ext): def get_library_names(self) -> list[str]: return ["gtest"] @@ -80,14 +126,10 @@ def run(self) -> None: compiler = ccompiler.new_compiler() sysconfig.customize_compiler(compiler) cflags: list[str] = [] - if compiler.compiler_type == "unix": + if compiler.compiler_type == "unix": # type: ignore[attr-defined] cflags += ["-O3"] - if X86_64: - cflags.append("-msse4.2") # Enable SIMD (see also mypyc/build.py) - elif compiler.compiler_type == "msvc": + elif compiler.compiler_type == "msvc": # type: ignore[attr-defined] cflags += ["/O2"] - if X86_64: - cflags.append("/arch:SSE4.2") # Enable SIMD (see also mypyc/build.py) setup( ext_modules=[ diff --git a/setup.py b/setup.py index 0037624f9bbc..f20c1db5d045 100644 --- a/setup.py +++ b/setup.py @@ -99,6 +99,7 @@ def run(self) -> None: os.path.join("mypyc", "lib-rt", "setup.py"), # Uses __file__ at top level https://github.com/mypyc/mypyc/issues/700 os.path.join("mypyc", "__main__.py"), + os.path.join("mypyc", "build_setup.py"), # for monkeypatching ) everything = [os.path.join("mypy", x) for x in find_package_data("mypy", ["*.py"])] + [ From 3c813083b27c87cf3a32e7422191b02bf59fab6e Mon Sep 17 00:00:00 2001 From: Piotr Sawicki Date: Thu, 27 Nov 2025 11:50:14 +0100 Subject: [PATCH 04/18] Add draft version of 1.19 release notes (#20296) Added a draft version with the commits filtered to remove internal changes and grouped into sections. --------- Co-authored-by: Jacopo Abramo --- CHANGELOG.md | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 134d251d90b1..ec3f0cbb59bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,156 @@ ## Next Release +## Mypy 1.19 (Unreleased) + +We’ve just uploaded mypy 1.19.0 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). +Mypy is a static type checker for Python. This release includes new features, performance +improvements and bug fixes. You can install it as follows: + + python3 -m pip install -U mypy + +You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io). + +### Performance improvements +- Switch to a more dynamic SCC processing logic (Ivan Levkivskyi, PR [20053](https://github.com/python/mypy/pull/20053)) +- Try some aliases speed-up (Ivan Levkivskyi, PR [19810](https://github.com/python/mypy/pull/19810)) + +### Fixed‑Format Cache +- Force-discard cache if cache format changed (Ivan Levkivskyi, PR [20152](https://github.com/python/mypy/pull/20152)) +- Use more efficient serialization format for long integers in cache files (Jukka Lehtosalo, PR [20151](https://github.com/python/mypy/pull/20151)) +- More robust packing of flats in FF cache (Ivan Levkivskyi, PR [20150](https://github.com/python/mypy/pull/20150)) +- Use self-descriptive cache with type tags (Ivan Levkivskyi, PR [20137](https://github.com/python/mypy/pull/20137)) +- Use fixed format for cache metas (Ivan Levkivskyi, PR [20088](https://github.com/python/mypy/pull/20088)) +- Make metas more compact; fix indirect suppression (Ivan Levkivskyi, PR [20075](https://github.com/python/mypy/pull/20075)) +- Add tool to convert binary cache files to JSON (Jukka Lehtosalo, PR [20071](https://github.com/python/mypy/pull/20071)) +- Use dedicated tags for most common instances (Ivan Levkivskyi, PR [19762](https://github.com/python/mypy/pull/19762)) + +### PEP 747 - Annotating Type Forms +- [PEP 747] Recognize `TypeForm[T]` type and values (#9773) (David Foster, PR [19596](https://github.com/python/mypy/pull/19596)) + +### Fixes to crashes +- Do not push partial types to the binder (Stanislav Terliakov, PR [20202](https://github.com/python/mypy/pull/20202)) +- Fix crash on recursive tuple with Hashable (Ivan Levkivskyi, PR [20232](https://github.com/python/mypy/pull/20232)) +- Do not assume that args of decorated functions can be cleanly mapped to their nodes (Stanislav Terliakov, PR [20203](https://github.com/python/mypy/pull/20203)) +- Do not abort constructing TypeAlias if only type parameters hold us back (Stanislav Terliakov, PR [20162](https://github.com/python/mypy/pull/20162)) +- Use the fallback for `ModuleSpec` early if it can never be resolved (Stanislav Terliakov, PR [20167](https://github.com/python/mypy/pull/20167)) +- Do not store deferred NamedTuple fields as redefinitions (Stanislav Terliakov, PR [20147](https://github.com/python/mypy/pull/20147)) +- Discard partials remaining after inference failure (Stanislav Terliakov, PR [20126](https://github.com/python/mypy/pull/20126)) +- Remember the pair in `is_overlapping_types` if at least one of them is an alias (Stanislav Terliakov, PR [20127](https://github.com/python/mypy/pull/20127)) +- Fix IsADirectoryError for namespace packages when using --linecoverage-report (wyattscarpenter, PR [20109](https://github.com/python/mypy/pull/20109)) +- Fix an INTERNAL ERROR when creating cobertura output for namespace package (wyattscarpenter, PR [20112](https://github.com/python/mypy/pull/20112)) +- Allow type parameters reusing the name missing from current module (Stanislav Terliakov, PR [20081](https://github.com/python/mypy/pull/20081)) +- Prevent TypeGuardedType leak from `narrow_declared_type` as part of typevar bound (Stanislav Terliakov, PR [20046](https://github.com/python/mypy/pull/20046)) +- Fix crash on invalid unpack in base class (Ivan Levkivskyi, PR [19962](https://github.com/python/mypy/pull/19962)) +- Traverse ParamSpec prefix where we should (Ivan Levkivskyi, PR [19800](https://github.com/python/mypy/pull/19800)) + +### Mypyc: Support for `__getattr__`, `__setattr__`, and `__delattr__` +- Support deleting attributes in `__setattr__` wrapper (Piotr Sawicki, PR [19997](https://github.com/python/mypy/pull/19997)) +- Generate `__setattr__` wrapper (Piotr Sawicki, PR [19937](https://github.com/python/mypy/pull/19937)) +- Generate `__getattr__` wrapper (Piotr Sawicki, PR [19909](https://github.com/python/mypy/pull/19909)) + +### Miscellaneous Mypyc Improvements +- Fix crash on `super` in generator (Ivan Levkivskyi, PR [20291](https://github.com/python/mypy/pull/20291)) +- Fix calling base class async method using `super()` (Jukka Lehtosalo, PR [20254](https://github.com/python/mypy/pull/20254)) +- Fix async or generator methods in traits (Jukka Lehtosalo, PR [20246](https://github.com/python/mypy/pull/20246)) +- Optimize equality check with string literals [1/1] (BobTheBuidler, PR [19883](https://github.com/python/mypy/pull/19883)) +- Fix inheritance of async defs (Jukka Lehtosalo, PR [20044](https://github.com/python/mypy/pull/20044)) +- Reject invalid `mypyc_attr` args [1/1] (BobTheBuidler, PR [19963](https://github.com/python/mypy/pull/19963)) +- Optimize `isinstance` with tuple of primitive types (BobTheBuidler, PR [19949](https://github.com/python/mypy/pull/19949)) +- Optimize away first index check in for loops if length > 1 (BobTheBuidler, PR [19933](https://github.com/python/mypy/pull/19933)) +- Fix broken exception/cancellation handling in async def (Jukka Lehtosalo, PR [19951](https://github.com/python/mypy/pull/19951)) +- Transform `object.__new__` inside `__new__` (Piotr Sawicki, PR [19866](https://github.com/python/mypy/pull/19866)) +- Fix crash with NewType and other non-class types in incremental builds (Jukka Lehtosalo, PR [19837](https://github.com/python/mypy/pull/19837)) +- Optimize container creation from expressions with length known at compile time (BobTheBuidler, PR [19503](https://github.com/python/mypy/pull/19503)) +- Allow per-class free list to be used with inheritance (Jukka Lehtosalo, PR [19790](https://github.com/python/mypy/pull/19790)) +- Fix object finalization (Marc Mueller, PR [19749](https://github.com/python/mypy/pull/19749)) +- Allow defining a single-item free "list" for a native class (Jukka Lehtosalo, PR [19785](https://github.com/python/mypy/pull/19785)) +- Speed up unary "not" (Jukka Lehtosalo, PR [19774](https://github.com/python/mypy/pull/19774)) + +### Stubtest Improvements +- Check `_value_` for ellipsis-valued stub enum members (Stanislav Terliakov, PR [19760](https://github.com/python/mypy/pull/19760)) +- Include function name in overload assertion messages (Joren Hammudoglu, PR [20063](https://github.com/python/mypy/pull/20063)) +- Small fix in get_default_function_sig (iap, PR [19822](https://github.com/python/mypy/pull/19822)) +- Adjust stubtest test stubs for PEP 728 (Python 3.15) (Marc Mueller, PR [20009](https://github.com/python/mypy/pull/20009)) +- Improve `allowlist` docs with better example (sobolevn, PR [20007](https://github.com/python/mypy/pull/20007)) + +### Documentation Updates +- Update duck_type_compatibility.rst: mention strict-bytes & mypy 2.0 (wyattscarpenter, PR [20121](https://github.com/python/mypy/pull/20121)) +- document --enable-incomplete-feature TypeForm, minimally but sufficiently (wyattscarpenter, PR [20173](https://github.com/python/mypy/pull/20173)) +- Change the InlineTypedDict example (wyattscarpenter, PR [20172](https://github.com/python/mypy/pull/20172)) +- Update kinds_of_types.rst: keep old anchor for #no-strict-optional (wyattscarpenter, PR [19828](https://github.com/python/mypy/pull/19828)) +- Replace `List` with built‑in `list` (PEP 585) (Thiago J. Barbalho, PR [20000](https://github.com/python/mypy/pull/20000)) +- main.py: junit documentation elaboration (wyattscarpenter, PR [19867](https://github.com/python/mypy/pull/19867)) + +### Other Notable Fixes and Improvements +- Update import map when new modules added (Ivan Levkivskyi, PR [20271](https://github.com/python/mypy/pull/20271)) +- Fix annotated with function as type keyword list parameter (KarelKenens, PR [20094](https://github.com/python/mypy/pull/20094)) +- Fix errors for raise NotImplemented (Shantanu, PR [20168](https://github.com/python/mypy/pull/20168)) +- Don't let help formatter line-wrap URLs (Frank Dana, PR [19825](https://github.com/python/mypy/pull/19825)) +- Do not cache fast container types inside lambdas (Stanislav Terliakov, PR [20166](https://github.com/python/mypy/pull/20166)) +- Respect force-union-syntax flag in error hint (Marc Mueller, PR [20165](https://github.com/python/mypy/pull/20165)) +- Fix type checking of dict type aliases (Shantanu, PR [20170](https://github.com/python/mypy/pull/20170)) +- Use pretty_callable more often for callable expressions (Theodore Ando, PR [20128](https://github.com/python/mypy/pull/20128)) +- Use dummy concrete type instead of `Any` when checking protocol variance (bzoracler, PR [20110](https://github.com/python/mypy/pull/20110)) +- [PEP 696] Fix swapping TypeVars with defaults (Randolf Scholz, PR [19449](https://github.com/python/mypy/pull/19449)) +- Fix narrowing of class pattern with union-argument (Randolf Scholz, PR [19517](https://github.com/python/mypy/pull/19517)) +- Do not emit unreachable warnings for lines that return `NotImplemented` (Christoph Tyralla, PR [20083](https://github.com/python/mypy/pull/20083)) +- fix matching against `typing.Callable` and `Protocol` types (Randolf Scholz, PR [19471](https://github.com/python/mypy/pull/19471)) +- Make --pretty work better on multi-line issues (A5rocks, PR [20056](https://github.com/python/mypy/pull/20056)) +- More precise return types for `TypedDict.get` (Randolf Scholz, PR [19897](https://github.com/python/mypy/pull/19897)) +- prevent false unreachable warnings for @final instances that occur when strict optional checking is disabled (Christoph Tyralla, PR [20045](https://github.com/python/mypy/pull/20045)) +- Check class references to catch non-existent classes in match cases (A5rocks, PR [20042](https://github.com/python/mypy/pull/20042)) +- Do not sort unused error codes in unused error codes warning (wyattscarpenter, PR [20036](https://github.com/python/mypy/pull/20036)) +- Fix `[name-defined]` false-positive in `class A[X, Y=X]:` case (sobolevn, PR [20021](https://github.com/python/mypy/pull/20021)) +- Filter SyntaxWarnings during AST parsing (Marc Mueller, PR [20023](https://github.com/python/mypy/pull/20023)) +- Make untyped decorator its own code (wyattscarpenter, PR [19911](https://github.com/python/mypy/pull/19911)) +- Support error codes from plugins in options (Sigve Sebastian Farstad, PR [19719](https://github.com/python/mypy/pull/19719)) +- Allow returning Literals in `__new__` (James Hilton-Balfe, PR [15687](https://github.com/python/mypy/pull/15687)) +- Inverse interface freshness logic (Ivan Levkivskyi, PR [19809](https://github.com/python/mypy/pull/19809)) +- Do not report exhaustive-match after deferral (Stanislav Terliakov, PR [19804](https://github.com/python/mypy/pull/19804)) +- Make untyped_calls_exclude invalidate cache (Ivan Levkivskyi, PR [19801](https://github.com/python/mypy/pull/19801)) +- Add await to empty context hack (Stanislav Terliakov, PR [19777](https://github.com/python/mypy/pull/19777)) +- Consider non-empty enums assignable to Self (Stanislav Terliakov, PR [19779](https://github.com/python/mypy/pull/19779)) + +### Typeshed updates + +Please see [git log](https://github.com/python/typeshed/commits/main?after=ebce8d766b41fbf4d83cf47c1297563a9508ff60+0&branch=main&path=stdlib) for full list of standard library typeshed stub changes. + +### Acknowledgements + +Thanks to all mypy contributors who contributed to this release: +- A5rocks +- BobTheBuidler +- bzoracler +- Chainfire +- Christoph Tyralla +- David Foster +- Frank Dana +- Guo Ci +- iap +- Ivan Levkivskyi +- James Hilton-Balfe +- jhance +- Joren Hammudoglu +- Jukka Lehtosalo +- KarelKenens +- Kevin Kannammalil +- Marc Mueller +- Michael Carlstrom +- Michael J. Sullivan +- Piotr Sawicki +- Randolf Scholz +- Shantanu +- Sigve Sebastian Farstad +- sobolevn +- Stanislav Terliakov +- Stephen Morton +- Theodore Ando +- Thiago J. Barbalho +- wyattscarpenter + +I’d also like to thank my employer, Dropbox, for supporting mypy development. + ## Mypy 1.18.1 We’ve just uploaded mypy 1.18.1 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). From 6d5cf52e67da306b62455cdce4ce9a9ccec35d02 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 28 Nov 2025 11:53:21 +0000 Subject: [PATCH 05/18] Various updates to 1.19 changelog (#20304) The first draft was added in #20296. --- CHANGELOG.md | 155 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 120 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec3f0cbb59bf..0be81310c6e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Next Release -## Mypy 1.19 (Unreleased) +## Mypy 1.19 We’ve just uploaded mypy 1.19.0 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). Mypy is a static type checker for Python. This release includes new features, performance @@ -12,51 +12,139 @@ improvements and bug fixes. You can install it as follows: You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io). -### Performance improvements +### Performance Improvements - Switch to a more dynamic SCC processing logic (Ivan Levkivskyi, PR [20053](https://github.com/python/mypy/pull/20053)) -- Try some aliases speed-up (Ivan Levkivskyi, PR [19810](https://github.com/python/mypy/pull/19810)) +- Speed up type aliases (Ivan Levkivskyi, PR [19810](https://github.com/python/mypy/pull/19810)) + +### Fixed‑Format Cache Improvements + +Mypy uses a cache by default to speed up incremental runs by reusing partial results +from earlier runs. Mypy 1.18 added a new binary fixed-format cache representation as +an experimental feature. The feature is no longer experimental, and we are planning +to enable it by default in a future mypy release (possibly 1.20), since it's faster +and uses less space than the original, JSON-based cache format. Use +`--fixed-format-cache` to enable the fixed-format cache. + +Mypy now has an extra dependency on the `librt` PyPI package, as it's needed for +cache serialization and deserialization. + +Mypy ships with a tool to convert fixed-format cache files to the old JSON format. +Example of how to use this: +``` +$ python -m mypy.exportjson .mypy_cache/.../my_module.data.ff +``` + +This way existing use cases that parse JSON cache files can be supported when using +the new format, though an extra conversion step is needed. + +This release includes these improvements: -### Fixed‑Format Cache - Force-discard cache if cache format changed (Ivan Levkivskyi, PR [20152](https://github.com/python/mypy/pull/20152)) +- Add tool to convert binary cache files to JSON (Jukka Lehtosalo, PR [20071](https://github.com/python/mypy/pull/20071)) - Use more efficient serialization format for long integers in cache files (Jukka Lehtosalo, PR [20151](https://github.com/python/mypy/pull/20151)) -- More robust packing of flats in FF cache (Ivan Levkivskyi, PR [20150](https://github.com/python/mypy/pull/20150)) +- More robust packing of floats in fixed-format cache (Ivan Levkivskyi, PR [20150](https://github.com/python/mypy/pull/20150)) - Use self-descriptive cache with type tags (Ivan Levkivskyi, PR [20137](https://github.com/python/mypy/pull/20137)) - Use fixed format for cache metas (Ivan Levkivskyi, PR [20088](https://github.com/python/mypy/pull/20088)) - Make metas more compact; fix indirect suppression (Ivan Levkivskyi, PR [20075](https://github.com/python/mypy/pull/20075)) -- Add tool to convert binary cache files to JSON (Jukka Lehtosalo, PR [20071](https://github.com/python/mypy/pull/20071)) -- Use dedicated tags for most common instances (Ivan Levkivskyi, PR [19762](https://github.com/python/mypy/pull/19762)) +- Use dedicated tags for most common cached instances (Ivan Levkivskyi, PR [19762](https://github.com/python/mypy/pull/19762)) + +### PEP 747: Annotating Type Forms + +Mypy now recognizes `TypeForm[T]` as a type and implements +[PEP 747](https://peps.python.org/pep-0747/). The feature is still experimental, +and it's disabled by default. Use `--enable-incomplete-feature=TypeForm` to +enable type forms. A type form object captures the type information provided by a +runtime type expression. Example: + +```python +from typing_extensions import TypeForm -### PEP 747 - Annotating Type Forms -- [PEP 747] Recognize `TypeForm[T]` type and values (#9773) (David Foster, PR [19596](https://github.com/python/mypy/pull/19596)) +def trycast[T](typx: TypeForm[T], value: object) -> T | None: ... -### Fixes to crashes +def example(o: object) -> None: + # 'int | str' below is an expression that represents a type. + # Unlike type[T], TypeForm[T] can be used with all kinds of types, + # including union types. + x = trycast(int | str, o) + if x is not None: + # Type of 'x' is 'int | str' here + ... +``` + +This feature was contributed by David Foster (PR [19596](https://github.com/python/mypy/pull/19596)). + +### Fixes to Crashes - Do not push partial types to the binder (Stanislav Terliakov, PR [20202](https://github.com/python/mypy/pull/20202)) - Fix crash on recursive tuple with Hashable (Ivan Levkivskyi, PR [20232](https://github.com/python/mypy/pull/20232)) -- Do not assume that args of decorated functions can be cleanly mapped to their nodes (Stanislav Terliakov, PR [20203](https://github.com/python/mypy/pull/20203)) +- Fix crash related to decorated functions (Stanislav Terliakov, PR [20203](https://github.com/python/mypy/pull/20203)) - Do not abort constructing TypeAlias if only type parameters hold us back (Stanislav Terliakov, PR [20162](https://github.com/python/mypy/pull/20162)) - Use the fallback for `ModuleSpec` early if it can never be resolved (Stanislav Terliakov, PR [20167](https://github.com/python/mypy/pull/20167)) - Do not store deferred NamedTuple fields as redefinitions (Stanislav Terliakov, PR [20147](https://github.com/python/mypy/pull/20147)) -- Discard partials remaining after inference failure (Stanislav Terliakov, PR [20126](https://github.com/python/mypy/pull/20126)) -- Remember the pair in `is_overlapping_types` if at least one of them is an alias (Stanislav Terliakov, PR [20127](https://github.com/python/mypy/pull/20127)) +- Discard partial types remaining after inference failure (Stanislav Terliakov, PR [20126](https://github.com/python/mypy/pull/20126)) +- Fix an infinite recursion bug (Stanislav Terliakov, PR [20127](https://github.com/python/mypy/pull/20127)) - Fix IsADirectoryError for namespace packages when using --linecoverage-report (wyattscarpenter, PR [20109](https://github.com/python/mypy/pull/20109)) -- Fix an INTERNAL ERROR when creating cobertura output for namespace package (wyattscarpenter, PR [20112](https://github.com/python/mypy/pull/20112)) +- Fix an internal error when creating cobertura output for namespace package (wyattscarpenter, PR [20112](https://github.com/python/mypy/pull/20112)) - Allow type parameters reusing the name missing from current module (Stanislav Terliakov, PR [20081](https://github.com/python/mypy/pull/20081)) -- Prevent TypeGuardedType leak from `narrow_declared_type` as part of typevar bound (Stanislav Terliakov, PR [20046](https://github.com/python/mypy/pull/20046)) +- Prevent TypeGuardedType leak from narrowing declared type as part of type variable bound (Stanislav Terliakov, PR [20046](https://github.com/python/mypy/pull/20046)) - Fix crash on invalid unpack in base class (Ivan Levkivskyi, PR [19962](https://github.com/python/mypy/pull/19962)) - Traverse ParamSpec prefix where we should (Ivan Levkivskyi, PR [19800](https://github.com/python/mypy/pull/19800)) +- Fix daemon crash related to imports (Ivan Levkivskyi, PR [20271](https://github.com/python/mypy/pull/20271)) ### Mypyc: Support for `__getattr__`, `__setattr__`, and `__delattr__` -- Support deleting attributes in `__setattr__` wrapper (Piotr Sawicki, PR [19997](https://github.com/python/mypy/pull/19997)) + +Mypyc now has partial support for `__getattr__`, `__setattr__` and +`__delattr__` methods in native classes. + +Note that native attributes are not stored using `__dict__`. Setting attributes +directly while bypassing `__setattr__` is possible by using +`super().__setattr__(...)` or `object.__setattr__(...)`, but not via `__dict__`. + +Example: +```python +class Demo: + _data: dict[str, str] + + def __init__(self) -> None: + # Initialize data dict without calling our __setattr__ + super().__setattr__("_data", {}) + + def __setattr__(self, name: str, value: str) -> None: + print(f"Setting {name} = {value!r}") + + if name == "_data": + raise AttributeError("'_data' cannot be set") + + self._data[name] = value + + def __getattr__(self, name: str) -> str: + print(f"Getting {name}") + + try: + return self._data[name] + except KeyError: + raise AttributeError(name) + +d = Demo() +d.x = "hello" +d.y = "world" + +print(d.x) +print(d.y) +``` + +Related PRs: - Generate `__setattr__` wrapper (Piotr Sawicki, PR [19937](https://github.com/python/mypy/pull/19937)) - Generate `__getattr__` wrapper (Piotr Sawicki, PR [19909](https://github.com/python/mypy/pull/19909)) +- Support deleting attributes in `__setattr__` wrapper (Piotr Sawicki, PR [19997](https://github.com/python/mypy/pull/19997)) ### Miscellaneous Mypyc Improvements +- Fix `__new__` in native classes with inheritance (Piotr Sawicki, PR [20302](https://github.com/python/mypy/pull/20302)) - Fix crash on `super` in generator (Ivan Levkivskyi, PR [20291](https://github.com/python/mypy/pull/20291)) - Fix calling base class async method using `super()` (Jukka Lehtosalo, PR [20254](https://github.com/python/mypy/pull/20254)) - Fix async or generator methods in traits (Jukka Lehtosalo, PR [20246](https://github.com/python/mypy/pull/20246)) -- Optimize equality check with string literals [1/1] (BobTheBuidler, PR [19883](https://github.com/python/mypy/pull/19883)) +- Optimize equality check with string literals (BobTheBuidler, PR [19883](https://github.com/python/mypy/pull/19883)) - Fix inheritance of async defs (Jukka Lehtosalo, PR [20044](https://github.com/python/mypy/pull/20044)) -- Reject invalid `mypyc_attr` args [1/1] (BobTheBuidler, PR [19963](https://github.com/python/mypy/pull/19963)) +- Reject invalid `mypyc_attr` args (BobTheBuidler, PR [19963](https://github.com/python/mypy/pull/19963)) - Optimize `isinstance` with tuple of primitive types (BobTheBuidler, PR [19949](https://github.com/python/mypy/pull/19949)) - Optimize away first index check in for loops if length > 1 (BobTheBuidler, PR [19933](https://github.com/python/mypy/pull/19933)) - Fix broken exception/cancellation handling in async def (Jukka Lehtosalo, PR [19951](https://github.com/python/mypy/pull/19951)) @@ -71,45 +159,42 @@ You can read the full documentation for this release on [Read the Docs](http://m ### Stubtest Improvements - Check `_value_` for ellipsis-valued stub enum members (Stanislav Terliakov, PR [19760](https://github.com/python/mypy/pull/19760)) - Include function name in overload assertion messages (Joren Hammudoglu, PR [20063](https://github.com/python/mypy/pull/20063)) -- Small fix in get_default_function_sig (iap, PR [19822](https://github.com/python/mypy/pull/19822)) -- Adjust stubtest test stubs for PEP 728 (Python 3.15) (Marc Mueller, PR [20009](https://github.com/python/mypy/pull/20009)) +- Fix special case in analyzing function signature (iap, PR [19822](https://github.com/python/mypy/pull/19822)) - Improve `allowlist` docs with better example (sobolevn, PR [20007](https://github.com/python/mypy/pull/20007)) ### Documentation Updates -- Update duck_type_compatibility.rst: mention strict-bytes & mypy 2.0 (wyattscarpenter, PR [20121](https://github.com/python/mypy/pull/20121)) -- document --enable-incomplete-feature TypeForm, minimally but sufficiently (wyattscarpenter, PR [20173](https://github.com/python/mypy/pull/20173)) -- Change the InlineTypedDict example (wyattscarpenter, PR [20172](https://github.com/python/mypy/pull/20172)) -- Update kinds_of_types.rst: keep old anchor for #no-strict-optional (wyattscarpenter, PR [19828](https://github.com/python/mypy/pull/19828)) +- Update duck type compatibility: mention strict-bytes and mypy 2.0 (wyattscarpenter, PR [20121](https://github.com/python/mypy/pull/20121)) +- Document `--enable-incomplete-feature TypeForm` (wyattscarpenter, PR [20173](https://github.com/python/mypy/pull/20173)) +- Change the inline TypedDict example (wyattscarpenter, PR [20172](https://github.com/python/mypy/pull/20172)) - Replace `List` with built‑in `list` (PEP 585) (Thiago J. Barbalho, PR [20000](https://github.com/python/mypy/pull/20000)) -- main.py: junit documentation elaboration (wyattscarpenter, PR [19867](https://github.com/python/mypy/pull/19867)) +- Improve junit documentation (wyattscarpenter, PR [19867](https://github.com/python/mypy/pull/19867)) ### Other Notable Fixes and Improvements -- Update import map when new modules added (Ivan Levkivskyi, PR [20271](https://github.com/python/mypy/pull/20271)) - Fix annotated with function as type keyword list parameter (KarelKenens, PR [20094](https://github.com/python/mypy/pull/20094)) - Fix errors for raise NotImplemented (Shantanu, PR [20168](https://github.com/python/mypy/pull/20168)) - Don't let help formatter line-wrap URLs (Frank Dana, PR [19825](https://github.com/python/mypy/pull/19825)) - Do not cache fast container types inside lambdas (Stanislav Terliakov, PR [20166](https://github.com/python/mypy/pull/20166)) - Respect force-union-syntax flag in error hint (Marc Mueller, PR [20165](https://github.com/python/mypy/pull/20165)) - Fix type checking of dict type aliases (Shantanu, PR [20170](https://github.com/python/mypy/pull/20170)) -- Use pretty_callable more often for callable expressions (Theodore Ando, PR [20128](https://github.com/python/mypy/pull/20128)) +- Use pretty callable formatting more often for callable expressions (Theodore Ando, PR [20128](https://github.com/python/mypy/pull/20128)) - Use dummy concrete type instead of `Any` when checking protocol variance (bzoracler, PR [20110](https://github.com/python/mypy/pull/20110)) -- [PEP 696] Fix swapping TypeVars with defaults (Randolf Scholz, PR [19449](https://github.com/python/mypy/pull/19449)) -- Fix narrowing of class pattern with union-argument (Randolf Scholz, PR [19517](https://github.com/python/mypy/pull/19517)) +- PEP 696: Fix swapping TypeVars with defaults (Randolf Scholz, PR [19449](https://github.com/python/mypy/pull/19449)) +- Fix narrowing of class pattern with union type (Randolf Scholz, PR [19517](https://github.com/python/mypy/pull/19517)) - Do not emit unreachable warnings for lines that return `NotImplemented` (Christoph Tyralla, PR [20083](https://github.com/python/mypy/pull/20083)) -- fix matching against `typing.Callable` and `Protocol` types (Randolf Scholz, PR [19471](https://github.com/python/mypy/pull/19471)) -- Make --pretty work better on multi-line issues (A5rocks, PR [20056](https://github.com/python/mypy/pull/20056)) +- Fix matching against `typing.Callable` and `Protocol` types (Randolf Scholz, PR [19471](https://github.com/python/mypy/pull/19471)) +- Make `--pretty` work better on multi-line issues (A5rocks, PR [20056](https://github.com/python/mypy/pull/20056)) - More precise return types for `TypedDict.get` (Randolf Scholz, PR [19897](https://github.com/python/mypy/pull/19897)) -- prevent false unreachable warnings for @final instances that occur when strict optional checking is disabled (Christoph Tyralla, PR [20045](https://github.com/python/mypy/pull/20045)) +- Prevent false unreachable warnings for `@final` instances that occur when strict optional checking is disabled (Christoph Tyralla, PR [20045](https://github.com/python/mypy/pull/20045)) - Check class references to catch non-existent classes in match cases (A5rocks, PR [20042](https://github.com/python/mypy/pull/20042)) - Do not sort unused error codes in unused error codes warning (wyattscarpenter, PR [20036](https://github.com/python/mypy/pull/20036)) -- Fix `[name-defined]` false-positive in `class A[X, Y=X]:` case (sobolevn, PR [20021](https://github.com/python/mypy/pull/20021)) +- Fix `[name-defined]` false positive in `class A[X, Y=X]:` case (sobolevn, PR [20021](https://github.com/python/mypy/pull/20021)) - Filter SyntaxWarnings during AST parsing (Marc Mueller, PR [20023](https://github.com/python/mypy/pull/20023)) -- Make untyped decorator its own code (wyattscarpenter, PR [19911](https://github.com/python/mypy/pull/19911)) +- Make untyped decorator its own error code (wyattscarpenter, PR [19911](https://github.com/python/mypy/pull/19911)) - Support error codes from plugins in options (Sigve Sebastian Farstad, PR [19719](https://github.com/python/mypy/pull/19719)) - Allow returning Literals in `__new__` (James Hilton-Balfe, PR [15687](https://github.com/python/mypy/pull/15687)) - Inverse interface freshness logic (Ivan Levkivskyi, PR [19809](https://github.com/python/mypy/pull/19809)) - Do not report exhaustive-match after deferral (Stanislav Terliakov, PR [19804](https://github.com/python/mypy/pull/19804)) -- Make untyped_calls_exclude invalidate cache (Ivan Levkivskyi, PR [19801](https://github.com/python/mypy/pull/19801)) +- Make `untyped_calls_exclude` invalidate cache (Ivan Levkivskyi, PR [19801](https://github.com/python/mypy/pull/19801)) - Add await to empty context hack (Stanislav Terliakov, PR [19777](https://github.com/python/mypy/pull/19777)) - Consider non-empty enums assignable to Self (Stanislav Terliakov, PR [19779](https://github.com/python/mypy/pull/19779)) From 0f068c9ec604daa09e69c92545b059f4b44f566e Mon Sep 17 00:00:00 2001 From: Piotr Sawicki Date: Fri, 28 Nov 2025 12:57:08 +0100 Subject: [PATCH 06/18] Remove +dev --- mypy/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/version.py b/mypy/version.py index af216bddded1..f550cd929eb5 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.19.0+dev" +__version__ = "1.19.0" base_version = __version__ mypy_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) From dbf97df271f3e69f0f52c9fc99e38a03ecadde79 Mon Sep 17 00:00:00 2001 From: Shantanu Jain Date: Sat, 13 Dec 2025 14:26:04 -0800 Subject: [PATCH 07/18] Bump version to 1.19.1+dev --- mypy/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/version.py b/mypy/version.py index f550cd929eb5..eef99f7203cf 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.19.0" +__version__ = "1.19.1+dev" base_version = __version__ mypy_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) From d503cf87a130449a053fd9ac098be7c9482ea540 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 1 Dec 2025 14:06:09 +0000 Subject: [PATCH 08/18] Fix crash on typevar with forward ref used in other module (#20334) Fixes https://github.com/python/mypy/issues/20326 Type variables with forward references in upper bound are known to be problematic. Existing mechanisms to work with them implicitly assumed that they are used in the same module where they are defined, which is not necessarily the case for "old-style" type variables that can be imported. Note that the simplification I made in `semanal_typeargs.py` would be probably sufficient to fix this, but that would be papering over the real issue, so I am making a bit more principled fix. --- mypy/plugins/proper_plugin.py | 1 + mypy/semanal.py | 2 +- mypy/semanal_typeargs.py | 12 ++-- mypy/typeanal.py | 9 +++ test-data/unit/check-type-aliases.test | 90 ++++++++++++++++++++++++++ 5 files changed, 105 insertions(+), 9 deletions(-) diff --git a/mypy/plugins/proper_plugin.py b/mypy/plugins/proper_plugin.py index 0189bfbd22fc..872903ea6b47 100644 --- a/mypy/plugins/proper_plugin.py +++ b/mypy/plugins/proper_plugin.py @@ -108,6 +108,7 @@ def is_special_target(right: ProperType) -> bool: "mypy.types.RequiredType", "mypy.types.ReadOnlyType", "mypy.types.TypeGuardedType", + "mypy.types.PlaceholderType", ): # Special case: these are not valid targets for a type alias and thus safe. # TODO: introduce a SyntheticType base to simplify this? diff --git a/mypy/semanal.py b/mypy/semanal.py index 973a28db0588..f9f0e4d71098 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -4935,7 +4935,7 @@ def get_typevarlike_argument( ) 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 + # 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]): diff --git a/mypy/semanal_typeargs.py b/mypy/semanal_typeargs.py index 86f8a8700def..9d1ce1fd6080 100644 --- a/mypy/semanal_typeargs.py +++ b/mypy/semanal_typeargs.py @@ -176,12 +176,12 @@ def validate_args( code=codes.VALID_TYPE, ) continue + if self.in_type_alias_expr and isinstance(arg, TypeVarType): + # Type aliases are allowed to use unconstrained type variables + # error will be checked at substitution point. + continue if tvar.values: if isinstance(arg, TypeVarType): - if self.in_type_alias_expr: - # Type aliases are allowed to use unconstrained type variables - # error will be checked at substitution point. - continue arg_values = arg.values if not arg_values: is_error = True @@ -205,10 +205,6 @@ def validate_args( and upper_bound.type.fullname == "builtins.object" ) if not object_upper_bound and not is_subtype(arg, upper_bound): - if self.in_type_alias_expr and isinstance(arg, TypeVarType): - # Type aliases are allowed to use unconstrained type variables - # error will be checked at substitution point. - continue is_error = True self.fail( message_registry.INVALID_TYPEVAR_ARG_BOUND.format( diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 06fa847c5434..3e5f522f3907 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -346,6 +346,15 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) if hook is not None: return hook(AnalyzeTypeContext(t, t, self)) tvar_def = self.tvar_scope.get_binding(sym) + if tvar_def is not None: + # We need to cover special-case explained in get_typevarlike_argument() here, + # since otherwise the deferral will not be triggered if the type variable is + # used in a different module. Using isinstance() should be safe for this purpose. + tvar_params = [tvar_def.upper_bound, tvar_def.default] + if isinstance(tvar_def, TypeVarType): + tvar_params += tvar_def.values + if any(isinstance(tp, PlaceholderType) for tp in tvar_params): + self.api.defer() if isinstance(sym.node, ParamSpecExpr): if tvar_def is None: if self.allow_unbound_tvars: diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index 6923b0d8f006..1fb2b038a1a1 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -1351,3 +1351,93 @@ reveal_type(D(x="asdf")) # E: No overload variant of "dict" matches argument ty # N: def __init__(self, arg: Iterable[tuple[str, int]], **kwargs: int) -> dict[str, int] \ # N: Revealed type is "Any" [builtins fixtures/dict.pyi] + +[case testTypeAliasesInCyclicImport1] +import p.aliases + +[file p/__init__.py] +[file p/aliases.py] +from typing_extensions import TypeAlias +from .defs import C, Alias1 + +Alias2: TypeAlias = Alias1[C] + +[file p/defs.py] +from typing import TypeVar +from typing_extensions import TypeAlias +import p.aliases + +C = TypeVar("C", bound="SomeClass") +Alias1: TypeAlias = C + +class SomeClass: + pass +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] + +[case testTypeAliasesInCyclicImport2] +import p.aliases + +[file p/__init__.py] +[file p/aliases.py] +from typing_extensions import TypeAlias +from .defs import C, Alias1 + +Alias2: TypeAlias = Alias1[C] + +[file p/defs.py] +from typing import TypeVar, Union +from typing_extensions import TypeAlias +import p.aliases + +C = TypeVar("C", bound="SomeClass") +Alias1: TypeAlias = Union[C, int] + +class SomeClass: + pass +[builtins fixtures/tuple.pyi] + +[case testTypeAliasesInCyclicImport3] +import p.aliases + +[file p/__init__.py] +[file p/aliases.py] +from typing_extensions import TypeAlias +from .defs import C, Alias1 + +Alias2: TypeAlias = Alias1[C] + +[file p/defs.py] +from typing import TypeVar +from typing_extensions import TypeAlias +import p.aliases + +C = TypeVar("C", bound="list[SomeClass]") +Alias1: TypeAlias = C + +class SomeClass: + pass +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] + +[case testTypeAliasesInCyclicImport4] +import p.aliases + +[file p/__init__.py] +[file p/aliases.py] +from typing_extensions import TypeAlias +from .defs import C, Alias1 + +Alias2: TypeAlias = Alias1[C] + +[file p/defs.py] +from typing import TypeVar, Union +from typing_extensions import TypeAlias +import p.aliases + +C = TypeVar("C", bound="list[SomeClass]") +Alias1: TypeAlias = Union[C, int] + +class SomeClass: + pass +[builtins fixtures/tuple.pyi] From c93d917a86993e06dcc88e508f28f4f5199ce1c8 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 30 Nov 2025 16:30:01 +0000 Subject: [PATCH 09/18] Fix crash on star import of redefinition (#20333) Fixes https://github.com/python/mypy/issues/20327 Fix is trivial, do not grab various internal/temporary symbols with star imports. This may create an invalid cross-reference (and is generally dangerous). Likely, this worked previously because we processed all fresh modules in queue, not just the dependencies of current SCC. --- mypy/semanal.py | 4 ++++ test-data/unit/check-incremental.test | 30 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index f9f0e4d71098..1035efb29061 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3195,6 +3195,10 @@ def visit_import_all(self, i: ImportAll) -> None: # namespace is incomplete. self.mark_incomplete("*", i) for name, node in m.names.items(): + if node.no_serialize: + # This is either internal or generated symbol, skip it to avoid problems + # like accidental name conflicts or invalid cross-references. + continue fullname = i_id + "." + name self.set_future_import_flags(fullname) # if '__all__' exists, all nodes not included have had module_public set to diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 56c9cef80f34..170a883ce25d 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -7596,3 +7596,33 @@ X = 0 tmp/a.py:6: error: "object" has no attribute "dtypes" [out2] tmp/a.py:2: error: "object" has no attribute "dtypes" + +[case testStarImportCycleRedefinition] +import m + +[file m.py] +import a + +[file m.py.2] +import a +reveal_type(a.C) + +[file a/__init__.py] +from a.b import * +from a.c import * +x = 1 + +[file a/b.py] +from other import C +from a.c import y +class C: ... # type: ignore + +[file a/c.py] +from other import C +from a import x +y = 1 + +[file other.py] +class C: ... +[out2] +tmp/m.py:2: note: Revealed type is "def () -> other.C" From 3890fc49bf7cc02db04b1e63eb2540aaacdeecc0 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sat, 29 Nov 2025 06:42:03 -0800 Subject: [PATCH 10/18] Fix crash involving Unpack-ed TypeVarTuple (#20323) Fixes #20093 This fixes the crash, but not the false positive (the false positive existed prior to the regression that introduced the crash) --- mypy/typeops.py | 10 ++++++++-- test-data/unit/check-overloading.test | 13 +++++++++++++ test-data/unit/check-typevar-tuple.test | 23 +++++++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/mypy/typeops.py b/mypy/typeops.py index 050252eb6205..f6646740031d 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -508,7 +508,7 @@ def erase_to_bound(t: Type) -> Type: def callable_corresponding_argument( typ: NormalizedCallableType | Parameters, model: FormalArgument ) -> FormalArgument | None: - """Return the argument a function that corresponds to `model`""" + """Return the argument of a function that corresponds to `model`""" by_name = typ.argument_by_name(model.name) by_pos = typ.argument_by_position(model.pos) @@ -522,17 +522,23 @@ def callable_corresponding_argument( # taking both *args and **args, or a pair of functions like so: # def right(a: int = ...) -> None: ... - # def left(__a: int = ..., *, a: int = ...) -> None: ... + # def left(x: int = ..., /, *, a: int = ...) -> None: ... from mypy.meet import meet_types if ( not (by_name.required or by_pos.required) and by_pos.name is None and by_name.pos is None + # This is not principled, but prevents a crash. It's weird to have a FormalArgument + # that has an UnpackType. + and not isinstance(by_name.typ, UnpackType) + and not isinstance(by_pos.typ, UnpackType) ): return FormalArgument( by_name.name, by_pos.pos, meet_types(by_name.typ, by_pos.typ), False ) + return by_name + return by_name if by_name is not None else by_pos diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index be55a182b87b..1830a0c5ce3c 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -263,6 +263,19 @@ def foo(*args: int | str, **kw: int | Foo) -> None: pass [builtins fixtures/tuple.pyi] + +[case testTypeCheckOverloadImplOverlapVarArgsAndKwargsNever] +from __future__ import annotations +from typing import overload + +@overload # E: Single overload definition, multiple required +def foo(x: int) -> None: ... + +def foo(*args: int, **kw: str) -> None: # E: Overloaded function implementation does not accept all possible arguments of signature 1 + pass +[builtins fixtures/tuple.pyi] + + [case testTypeCheckOverloadWithImplTooSpecificRetType] from typing import overload, Any diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index cb5029ee4e6d..c60d0aec0835 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -2716,3 +2716,26 @@ class MyTuple(tuple[Unpack[Union[int, str]]], Generic[Unpack[Ts]]): # E: "Union x: MyTuple[int, str] reveal_type(x[0]) # N: Revealed type is "Any" [builtins fixtures/tuple.pyi] + +[case testHigherOrderFunctionUnpackTypeVarTupleViaParamSpec] +from typing import Callable, ParamSpec, TypeVar, TypeVarTuple, Unpack + +P = ParamSpec("P") +T = TypeVar("T") +Ts = TypeVarTuple("Ts") + +def call(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T: + return func(*args, **kwargs) + + +def run(func: Callable[[Unpack[Ts]], T], *args: Unpack[Ts], some_kwarg: str = "asdf") -> T: + raise + + +def foo() -> str: + return "hello" + + +# this is a false positive, but it no longer crashes +call(run, foo, some_kwarg="a") # E: Argument 1 to "call" has incompatible type "def [Ts`-1, T] run(func: def (*Unpack[Ts]) -> T, *args: Unpack[Ts], some_kwarg: str = ...) -> T"; expected "Callable[[Callable[[], str], str], str]" +[builtins fixtures/tuple.pyi] From 70eceea682c041c0d8e8462dffef9c7bb252e014 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sat, 13 Dec 2025 14:24:20 -0800 Subject: [PATCH 11/18] Fix noncommutative joins with bounded TypeVars (#20345) Fixes #20344 --- mypy/join.py | 13 +++++++++---- mypy/test/testtypes.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/mypy/join.py b/mypy/join.py index 0822ddbfd89a..a074fa522588 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -297,10 +297,15 @@ def visit_erased_type(self, t: ErasedType) -> ProperType: return self.s def visit_type_var(self, t: TypeVarType) -> ProperType: - if isinstance(self.s, TypeVarType) and self.s.id == t.id: - if self.s.upper_bound == t.upper_bound: - return self.s - return self.s.copy_modified(upper_bound=join_types(self.s.upper_bound, t.upper_bound)) + if isinstance(self.s, TypeVarType): + if self.s.id == t.id: + if self.s.upper_bound == t.upper_bound: + return self.s + return self.s.copy_modified( + upper_bound=join_types(self.s.upper_bound, t.upper_bound) + ) + # Fix non-commutative joins + return get_proper_type(join_types(self.s.upper_bound, t.upper_bound)) else: return self.default(self.s) diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index fc68d9aa6eac..f5f4c6797db2 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -1051,6 +1051,35 @@ def test_join_type_type_type_var(self) -> None: self.assert_join(self.fx.type_a, self.fx.t, self.fx.o) self.assert_join(self.fx.t, self.fx.type_a, self.fx.o) + def test_join_type_var_bounds(self) -> None: + tvar1 = TypeVarType( + "tvar1", + "tvar1", + TypeVarId(-100), + [], + self.fx.o, + AnyType(TypeOfAny.from_omitted_generics), + INVARIANT, + ) + any_type = AnyType(TypeOfAny.special_form) + tvar2 = TypeVarType( + "tvar2", + "tvar2", + TypeVarId(-101), + [], + upper_bound=UnionType( + [ + TupleType([any_type], self.fx.std_tuple), + TupleType([any_type, any_type], self.fx.std_tuple), + ] + ), + default=AnyType(TypeOfAny.from_omitted_generics), + variance=INVARIANT, + ) + + self.assert_join(tvar1, tvar2, self.fx.o) + self.assert_join(tvar2, tvar1, self.fx.o) + # There are additional test cases in check-inference.test. # TODO: Function types + varargs and default args. From 8a6eff478416cd3ed3931a6ed77ce61c88ab69e9 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 9 Dec 2025 02:27:25 -0500 Subject: [PATCH 12/18] [mypyc] fix generator regression with empty tuple (#20371) This PR fixes #20341 --- mypyc/irbuild/builder.py | 4 ++- mypyc/irbuild/for_helpers.py | 42 +++++++++++++++++++++-------- mypyc/test-data/run-generators.test | 8 ++++++ mypyc/test-data/run-loops.test | 9 +++++-- 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index 63930123135f..51a02ed5446d 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -990,8 +990,10 @@ def get_sequence_type_from_type(self, target_type: Type) -> RType: elif isinstance(target_type, TypeVarLikeType): return self.get_sequence_type_from_type(target_type.upper_bound) elif isinstance(target_type, TupleType): + items = target_type.items + assert items, "This function does not support empty tuples" # Tuple might have elements of different types. - rtypes = {self.mapper.type_to_rtype(item) for item in target_type.items} + rtypes = set(map(self.mapper.type_to_rtype, items)) if len(rtypes) == 1: return rtypes.pop() else: diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 715f5432cd13..33e442935641 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -7,7 +7,7 @@ from __future__ import annotations -from typing import Callable, ClassVar +from typing import Callable, ClassVar, cast from mypy.nodes import ( ARG_POS, @@ -241,25 +241,45 @@ def sequence_from_generator_preallocate_helper( rtype = builder.node_type(sequence_expr) if not (is_sequence_rprimitive(rtype) or isinstance(rtype, RTuple)): return None - sequence = builder.accept(sequence_expr) - length = get_expr_length_value(builder, sequence_expr, sequence, line, use_pyssize_t=True) + if isinstance(rtype, RTuple): # If input is RTuple, box it to tuple_rprimitive for generic iteration # TODO: this can be optimized a bit better with an unrolled ForRTuple helper proper_type = get_proper_type(builder.types[sequence_expr]) assert isinstance(proper_type, TupleType), proper_type - get_item_ops = [ - ( - LoadLiteral(typ.value, object_rprimitive) - if isinstance(typ, LiteralType) - else TupleGet(sequence, i, line) - ) - for i, typ in enumerate(get_proper_types(proper_type.items)) - ] + # the for_loop_helper_with_index crashes for empty tuples, bail out + if not proper_type.items: + return None + + proper_types = get_proper_types(proper_type.items) + + get_item_ops: list[LoadLiteral | TupleGet] + if all(isinstance(typ, LiteralType) for typ in proper_types): + get_item_ops = [ + LoadLiteral(cast(LiteralType, typ).value, object_rprimitive) + for typ in proper_types + ] + + else: + sequence = builder.accept(sequence_expr) + get_item_ops = [ + ( + LoadLiteral(typ.value, object_rprimitive) + if isinstance(typ, LiteralType) + else TupleGet(sequence, i, line) + ) + for i, typ in enumerate(proper_types) + ] + items = list(map(builder.add, get_item_ops)) sequence = builder.new_tuple(items, line) + else: + sequence = builder.accept(sequence_expr) + + length = get_expr_length_value(builder, sequence_expr, sequence, line, use_pyssize_t=True) + target_op = empty_op_llbuilder(length, line) def set_item(item_index: Value) -> None: diff --git a/mypyc/test-data/run-generators.test b/mypyc/test-data/run-generators.test index c8e83173474d..cf1dac7c5733 100644 --- a/mypyc/test-data/run-generators.test +++ b/mypyc/test-data/run-generators.test @@ -936,3 +936,11 @@ def test_generator_override() -> None: assert base1_foo(Base1()) == [1] assert base1_foo(Derived1()) == [2, 3] assert derived1_foo(Derived1()) == [2, 3] + +[case testGeneratorEmptyTuple] +from collections.abc import Generator +from typing import Optional, Union + +def test_compiledGeneratorEmptyTuple() -> None: + jobs: Generator[Optional[str], None, None] = (_ for _ in ()) + assert list(jobs) == [] diff --git a/mypyc/test-data/run-loops.test b/mypyc/test-data/run-loops.test index 3cbb07297e6e..106c2271d326 100644 --- a/mypyc/test-data/run-loops.test +++ b/mypyc/test-data/run-loops.test @@ -1,7 +1,7 @@ # Test cases for "range" objects, "for" and "while" loops (compile and run) [case testFor] -from typing import List, Tuple +from typing import Any, List, Tuple def count(n: int) -> None: for i in range(n): print(i) @@ -21,6 +21,10 @@ def list_iter(l: List[int]) -> None: def tuple_iter(l: Tuple[int, ...]) -> None: for i in l: print(i) +def empty_tuple_iter(l: Tuple[()]) -> None: + i: Any + for i in l: + print(i) def str_iter(l: str) -> None: for i in l: print(i) @@ -39,7 +43,7 @@ def count_down_short() -> None: [file driver.py] from native import ( count, list_iter, list_rev_iter, list_rev_iter_lol, count_between, count_down, count_double, - count_down_short, tuple_iter, str_iter, + count_down_short, tuple_iter, empty_tuple_iter, str_iter, ) count(5) list_iter(list(reversed(range(5)))) @@ -52,6 +56,7 @@ count_down_short() print('==') list_rev_iter_lol(list(reversed(range(5)))) tuple_iter((1, 2, 3)) +empty_tuple_iter(()) str_iter("abc") [out] 0 From a4b31a26788b70c4a2a19adbafa2bbda43dc2e8b Mon Sep 17 00:00:00 2001 From: A5rocks Date: Tue, 9 Dec 2025 07:03:09 -0500 Subject: [PATCH 13/18] Allow `types.NoneType` in match cases (#20383) Fixes https://github.com/python/mypy/issues/20367 --- mypy/checkpattern.py | 3 +++ test-data/unit/check-python310.test | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/mypy/checkpattern.py b/mypy/checkpattern.py index 3c51c4106909..cafc69490e09 100644 --- a/mypy/checkpattern.py +++ b/mypy/checkpattern.py @@ -46,6 +46,7 @@ Type, TypedDictType, TypeOfAny, + TypeType, TypeVarTupleType, TypeVarType, UninhabitedType, @@ -556,6 +557,8 @@ def visit_class_pattern(self, o: ClassPattern) -> PatternType: fallback = self.chk.named_type("builtins.function") any_type = AnyType(TypeOfAny.unannotated) typ = callable_with_ellipsis(any_type, ret_type=any_type, fallback=fallback) + elif isinstance(p_typ, TypeType) and isinstance(p_typ.item, NoneType): + typ = p_typ.item elif not isinstance(p_typ, AnyType): self.msg.fail( message_registry.CLASS_PATTERN_TYPE_REQUIRED.format( diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 1e27e30d4b04..8bc781d091c3 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -3178,3 +3178,19 @@ match 5: reveal_type(b) # N: Revealed type is "Any" case BlahBlah(c=c): # E: Name "BlahBlah" is not defined reveal_type(c) # N: Revealed type is "Any" + +[case testMatchAllowsNoneTypeAsClass] +import types + +class V: + X = types.NoneType + +def fun(val: str | None): + match val: + case V.X(): + reveal_type(val) # N: Revealed type is "None" + + match val: + case types.NoneType(): + reveal_type(val) # N: Revealed type is "None" +[builtins fixtures/tuple.pyi] From 58d485b4ea4776e0b9d4045b306cb0818ecc2aa6 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 9 Dec 2025 14:08:13 +0000 Subject: [PATCH 14/18] Fail with an explicit error on PyPy (#20384) Fixes https://github.com/mypyc/librt/issues/21 Fail with an explicit user-friendly error on PyPy. --- mypy-requirements.txt | 2 +- pyproject.toml | 4 ++-- setup.py | 9 +++++++++ test-requirements.txt | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/mypy-requirements.txt b/mypy-requirements.txt index b0c632dddac5..6984d9a5d070 100644 --- a/mypy-requirements.txt +++ b/mypy-requirements.txt @@ -4,4 +4,4 @@ typing_extensions>=4.6.0 mypy_extensions>=1.0.0 pathspec>=0.9.0 tomli>=1.1.0; python_version<'3.11' -librt>=0.6.2 +librt>=0.6.2; platform_python_implementation != 'PyPy' diff --git a/pyproject.toml b/pyproject.toml index bb41c82b1a3c..fa56caeaa4bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ requires = [ "mypy_extensions>=1.0.0", "pathspec>=0.9.0", "tomli>=1.1.0; python_version<'3.11'", - "librt>=0.6.2", + "librt>=0.6.2; platform_python_implementation != 'PyPy'", # the following is from build-requirements.txt "types-psutil", "types-setuptools", @@ -54,7 +54,7 @@ dependencies = [ "mypy_extensions>=1.0.0", "pathspec>=0.9.0", "tomli>=1.1.0; python_version<'3.11'", - "librt>=0.6.2", + "librt>=0.6.2; platform_python_implementation != 'PyPy'", ] dynamic = ["version"] diff --git a/setup.py b/setup.py index f20c1db5d045..8cba27ae0f85 100644 --- a/setup.py +++ b/setup.py @@ -5,6 +5,7 @@ import glob import os import os.path +import platform import sys from typing import TYPE_CHECKING, Any @@ -12,6 +13,14 @@ sys.stderr.write("ERROR: You need Python 3.9 or later to use mypy.\n") exit(1) +if platform.python_implementation() == "PyPy": + sys.stderr.write( + "ERROR: Running mypy on PyPy is not supported yet.\n" + "To type-check a PyPy library please use an equivalent CPython version,\n" + "see https://github.com/mypyc/librt/issues/16 for possible workarounds.\n" + ) + exit(1) + # we'll import stuff from the source tree, let's ensure is on the sys path sys.path.insert(0, os.path.dirname(os.path.realpath(__file__))) diff --git a/test-requirements.txt b/test-requirements.txt index 953e7a750c75..d8334108fc1d 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -22,7 +22,7 @@ identify==2.6.15 # via pre-commit iniconfig==2.1.0 # via pytest -librt==0.6.2 +librt==0.7.3 ; platform_python_implementation != 'PyPy' # via -r mypy-requirements.txt lxml==6.0.2 ; python_version < "3.15" # via -r test-requirements.in From f60f90fb8872bf722e32aefd548daaf6d8560e05 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 10 Dec 2025 02:02:26 +0000 Subject: [PATCH 15/18] Fail on PyPy in main instead of setup.py (#20389) Follow-up for https://github.com/python/mypy/pull/20384 --- mypy/main.py | 8 ++++++++ setup.py | 9 --------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/mypy/main.py b/mypy/main.py index 7d5721851c3d..5b8f8b5a5476 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -4,6 +4,7 @@ import argparse import os +import platform import subprocess import sys import time @@ -39,6 +40,13 @@ if TYPE_CHECKING: from _typeshed import SupportsWrite +if platform.python_implementation() == "PyPy": + sys.stderr.write( + "ERROR: Running mypy on PyPy is not supported yet.\n" + "To type-check a PyPy library please use an equivalent CPython version,\n" + "see https://github.com/mypyc/librt/issues/16 for possible workarounds.\n" + ) + sys.exit(2) orig_stat: Final = os.stat MEM_PROFILE: Final = False # If True, dump memory profile diff --git a/setup.py b/setup.py index 8cba27ae0f85..f20c1db5d045 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,6 @@ import glob import os import os.path -import platform import sys from typing import TYPE_CHECKING, Any @@ -13,14 +12,6 @@ sys.stderr.write("ERROR: You need Python 3.9 or later to use mypy.\n") exit(1) -if platform.python_implementation() == "PyPy": - sys.stderr.write( - "ERROR: Running mypy on PyPy is not supported yet.\n" - "To type-check a PyPy library please use an equivalent CPython version,\n" - "see https://github.com/mypyc/librt/issues/16 for possible workarounds.\n" - ) - exit(1) - # we'll import stuff from the source tree, let's ensure is on the sys path sys.path.insert(0, os.path.dirname(os.path.realpath(__file__))) From 2b23b507524bf1bd7513eea6f2a16fb91e072cb6 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 10 Dec 2025 00:51:04 +0000 Subject: [PATCH 16/18] Serialize raw errors in cache metas (#20372) Fixes https://github.com/python/mypy/issues/20353 This makes us respect e.g. `--output json` for cached files without re-checking the files (which is the desired behavior for users, see issue). This is also a first step towards resolving the "foo defined here" conundrum for parallel checking. The fix is straightforward. The only question was whether to continue using `ErrorTuple`s or switch to a proper class. I decided to keep the tuples for now to minimize the scope of change. Note I am also adjusting generic "JSON" fixed-format helpers to natively support tuples (unlike real JSON). We already use tuples in few other places, so it makes sense to just make it "official" (this format is still internal to mypy obviously). --- mypy/build.py | 69 +++++++++++++++++++++---- mypy/cache.py | 72 +++++++++++++++++++++++---- mypy/errors.py | 25 ++++++---- test-data/unit/check-incremental.test | 10 ++++ 4 files changed, 146 insertions(+), 30 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 853e54e445ac..aee099fed316 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -31,10 +31,17 @@ from librt.internal import cache_version import mypy.semanal_main -from mypy.cache import CACHE_VERSION, CacheMeta, ReadBuffer, WriteBuffer +from mypy.cache import ( + CACHE_VERSION, + CacheMeta, + ReadBuffer, + SerializedError, + WriteBuffer, + write_json, +) from mypy.checker import TypeChecker from mypy.error_formatter import OUTPUT_CHOICES, ErrorFormatter -from mypy.errors import CompileError, ErrorInfo, Errors, report_internal_error +from mypy.errors import CompileError, ErrorInfo, Errors, ErrorTuple, report_internal_error from mypy.graph_utils import prepare_sccs, strongly_connected_components, topsort from mypy.indirection import TypeIndirectionVisitor from mypy.messages import MessageBuilder @@ -1869,7 +1876,7 @@ class State: dep_hashes: dict[str, bytes] = {} # List of errors reported for this file last time. - error_lines: list[str] = [] + error_lines: list[SerializedError] = [] # Parent package, its parent, etc. ancestors: list[str] | None = None @@ -3286,9 +3293,13 @@ def find_stale_sccs( scc = order_ascc_ex(graph, ascc) for id in scc: if graph[id].error_lines: - manager.flush_errors( - manager.errors.simplify_path(graph[id].xpath), graph[id].error_lines, False + path = manager.errors.simplify_path(graph[id].xpath) + formatted = manager.errors.format_messages( + path, + deserialize_codes(graph[id].error_lines), + formatter=manager.error_formatter, ) + manager.flush_errors(path, formatted, False) fresh_sccs.append(ascc) else: size = len(ascc.mod_ids) @@ -3492,13 +3503,16 @@ def process_stale_scc(graph: Graph, ascc: SCC, manager: BuildManager) -> None: # Flush errors, and write cache in two phases: first data files, then meta files. meta_tuples = {} errors_by_id = {} + formatted_by_id = {} for id in stale: if graph[id].xpath not in manager.errors.ignored_files: - errors = manager.errors.file_messages( - graph[id].xpath, formatter=manager.error_formatter + errors = manager.errors.file_messages(graph[id].xpath) + formatted = manager.errors.format_messages( + graph[id].xpath, errors, formatter=manager.error_formatter ) - manager.flush_errors(manager.errors.simplify_path(graph[id].xpath), errors, False) + manager.flush_errors(manager.errors.simplify_path(graph[id].xpath), formatted, False) errors_by_id[id] = errors + formatted_by_id[id] = formatted meta_tuples[id] = graph[id].write_cache() graph[id].mark_as_rechecked() for id in stale: @@ -3507,7 +3521,7 @@ def process_stale_scc(graph: Graph, ascc: SCC, manager: BuildManager) -> None: continue meta, meta_file = meta_tuple meta.dep_hashes = [graph[dep].interface_hash for dep in graph[id].dependencies] - meta.error_lines = errors_by_id.get(id, []) + meta.error_lines = serialize_codes(errors_by_id.get(id, [])) write_cache_meta(meta, manager, meta_file) manager.done_sccs.add(ascc.id) @@ -3640,3 +3654,40 @@ def write_undocumented_ref_info( deps_json = get_undocumented_ref_info_json(state.tree, type_map) metastore.write(ref_info_file, json_dumps(deps_json)) + + +def sources_to_bytes(sources: list[BuildSource]) -> bytes: + source_tuples = [(s.path, s.module, s.text, s.base_dir, s.followed) for s in sources] + buf = WriteBuffer() + write_json(buf, {"sources": source_tuples}) + return buf.getvalue() + + +def sccs_to_bytes(sccs: list[SCC]) -> bytes: + scc_tuples = [(list(scc.mod_ids), scc.id, list(scc.deps)) for scc in sccs] + buf = WriteBuffer() + write_json(buf, {"sccs": scc_tuples}) + return buf.getvalue() + + +def serialize_codes(errs: list[ErrorTuple]) -> list[SerializedError]: + return [ + (path, line, column, end_line, end_column, severity, message, code.code if code else None) + for path, line, column, end_line, end_column, severity, message, code in errs + ] + + +def deserialize_codes(errs: list[SerializedError]) -> list[ErrorTuple]: + return [ + ( + path, + line, + column, + end_line, + end_column, + severity, + message, + codes.error_codes.get(code) if code else None, + ) + for path, line, column, end_line, end_column, severity, message, code in errs + ] diff --git a/mypy/cache.py b/mypy/cache.py index ad12fd96f1fa..7755755898c0 100644 --- a/mypy/cache.py +++ b/mypy/cache.py @@ -48,7 +48,7 @@ from __future__ import annotations from collections.abc import Sequence -from typing import Any, Final, Union +from typing import Any, Final, Optional, Union from typing_extensions import TypeAlias as _TypeAlias from librt.internal import ( @@ -70,7 +70,9 @@ from mypy_extensions import u8 # High-level cache layout format -CACHE_VERSION: Final = 0 +CACHE_VERSION: Final = 1 + +SerializedError: _TypeAlias = tuple[Optional[str], int, int, int, int, str, str, Optional[str]] class CacheMeta: @@ -93,7 +95,7 @@ def __init__( dep_lines: list[int], dep_hashes: list[bytes], interface_hash: bytes, - error_lines: list[str], + error_lines: list[SerializedError], version_id: str, ignore_all: bool, plugin_data: Any, @@ -158,7 +160,7 @@ def deserialize(cls, meta: dict[str, Any], data_file: str) -> CacheMeta | None: dep_lines=meta["dep_lines"], dep_hashes=[bytes.fromhex(dep) for dep in meta["dep_hashes"]], interface_hash=bytes.fromhex(meta["interface_hash"]), - error_lines=meta["error_lines"], + error_lines=[tuple(err) for err in meta["error_lines"]], version_id=meta["version_id"], ignore_all=meta["ignore_all"], plugin_data=meta["plugin_data"], @@ -180,7 +182,7 @@ def write(self, data: WriteBuffer) -> None: write_int_list(data, self.dep_lines) write_bytes_list(data, self.dep_hashes) write_bytes(data, self.interface_hash) - write_str_list(data, self.error_lines) + write_errors(data, self.error_lines) write_str(data, self.version_id) write_bool(data, self.ignore_all) # Plugin data may be not a dictionary, so we use @@ -205,7 +207,7 @@ def read(cls, data: ReadBuffer, data_file: str) -> CacheMeta | None: dep_lines=read_int_list(data), dep_hashes=read_bytes_list(data), interface_hash=read_bytes(data), - error_lines=read_str_list(data), + error_lines=read_errors(data), version_id=read_str(data), ignore_all=read_bool(data), plugin_data=read_json_value(data), @@ -232,6 +234,7 @@ def read(cls, data: ReadBuffer, data_file: str) -> CacheMeta | None: LIST_INT: Final[Tag] = 21 LIST_STR: Final[Tag] = 22 LIST_BYTES: Final[Tag] = 23 +TUPLE_GEN: Final[Tag] = 24 DICT_STR_GEN: Final[Tag] = 30 # Misc classes. @@ -391,7 +394,13 @@ def write_str_opt_list(data: WriteBuffer, value: list[str | None]) -> None: write_str_opt(data, item) -JsonValue: _TypeAlias = Union[None, int, str, bool, list["JsonValue"], dict[str, "JsonValue"]] +Value: _TypeAlias = Union[None, int, str, bool] + +# Our JSON format is somewhat non-standard as we distinguish lists and tuples. +# This is convenient for some internal things, like mypyc plugin and error serialization. +JsonValue: _TypeAlias = Union[ + Value, list["JsonValue"], dict[str, "JsonValue"], tuple["JsonValue", ...] +] def read_json_value(data: ReadBuffer) -> JsonValue: @@ -409,15 +418,16 @@ def read_json_value(data: ReadBuffer) -> JsonValue: if tag == LIST_GEN: size = read_int_bare(data) return [read_json_value(data) for _ in range(size)] + if tag == TUPLE_GEN: + size = read_int_bare(data) + return tuple(read_json_value(data) for _ in range(size)) if tag == DICT_STR_GEN: size = read_int_bare(data) return {read_str_bare(data): read_json_value(data) for _ in range(size)} assert False, f"Invalid JSON tag: {tag}" -# Currently tuples are used by mypyc plugin. They will be normalized to -# JSON lists after a roundtrip. -def write_json_value(data: WriteBuffer, value: JsonValue | tuple[JsonValue, ...]) -> None: +def write_json_value(data: WriteBuffer, value: JsonValue) -> None: if value is None: write_tag(data, LITERAL_NONE) elif isinstance(value, bool): @@ -428,11 +438,16 @@ def write_json_value(data: WriteBuffer, value: JsonValue | tuple[JsonValue, ...] elif isinstance(value, str): write_tag(data, LITERAL_STR) write_str_bare(data, value) - elif isinstance(value, (list, tuple)): + elif isinstance(value, list): write_tag(data, LIST_GEN) write_int_bare(data, len(value)) for val in value: write_json_value(data, val) + elif isinstance(value, tuple): + write_tag(data, TUPLE_GEN) + write_int_bare(data, len(value)) + for val in value: + write_json_value(data, val) elif isinstance(value, dict): write_tag(data, DICT_STR_GEN) write_int_bare(data, len(value)) @@ -457,3 +472,38 @@ def write_json(data: WriteBuffer, value: dict[str, Any]) -> None: for key in sorted(value): write_str_bare(data, key) write_json_value(data, value[key]) + + +def write_errors(data: WriteBuffer, errs: list[SerializedError]) -> None: + write_tag(data, LIST_GEN) + write_int_bare(data, len(errs)) + for path, line, column, end_line, end_column, severity, message, code in errs: + write_tag(data, TUPLE_GEN) + write_str_opt(data, path) + write_int(data, line) + write_int(data, column) + write_int(data, end_line) + write_int(data, end_column) + write_str(data, severity) + write_str(data, message) + write_str_opt(data, code) + + +def read_errors(data: ReadBuffer) -> list[SerializedError]: + assert read_tag(data) == LIST_GEN + result = [] + for _ in range(read_int_bare(data)): + assert read_tag(data) == TUPLE_GEN + result.append( + ( + read_str_opt(data), + read_int(data), + read_int(data), + read_int(data), + read_int(data), + read_str(data), + read_str(data), + read_str_opt(data), + ) + ) + return result diff --git a/mypy/errors.py b/mypy/errors.py index 69e4fb4cf065..ce5c6cc8215f 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -951,7 +951,7 @@ def raise_error(self, use_stdout: bool = True) -> NoReturn: self.new_messages(), use_stdout=use_stdout, module_with_blocker=self.blocker_module() ) - def format_messages( + def format_messages_default( self, error_tuples: list[ErrorTuple], source_lines: list[str] | None ) -> list[str]: """Return a string list that represents the error messages. @@ -1009,24 +1009,28 @@ def format_messages( a.append(" " * (DEFAULT_SOURCE_OFFSET + column) + marker) return a - def file_messages(self, path: str, formatter: ErrorFormatter | None = None) -> list[str]: - """Return a string list of new error messages from a given file. - - Use a form suitable for displaying to the user. - """ + def file_messages(self, path: str) -> list[ErrorTuple]: + """Return an error tuple list of new error messages from a given file.""" if path not in self.error_info_map: return [] error_info = self.error_info_map[path] error_info = [info for info in error_info if not info.hidden] error_info = self.remove_duplicates(self.sort_messages(error_info)) - error_tuples = self.render_messages(error_info) + return self.render_messages(error_info) + def format_messages( + self, path: str, error_tuples: list[ErrorTuple], formatter: ErrorFormatter | None = None + ) -> list[str]: + """Return a string list of new error messages from a given file. + + Use a form suitable for displaying to the user. + """ + self.flushed_files.add(path) if formatter is not None: errors = create_errors(error_tuples) return [formatter.report_error(err) for err in errors] - self.flushed_files.add(path) source_lines = None if self.options.pretty and self.read_source: # Find shadow file mapping and read source lines if a shadow file exists for the given path. @@ -1036,7 +1040,7 @@ def file_messages(self, path: str, formatter: ErrorFormatter | None = None) -> l source_lines = self.read_source(mapped_path) else: source_lines = self.read_source(path) - return self.format_messages(error_tuples, source_lines) + return self.format_messages_default(error_tuples, source_lines) def find_shadow_file_mapping(self, path: str) -> str | None: """Return the shadow file path for a given source file path or None.""" @@ -1058,7 +1062,8 @@ def new_messages(self) -> list[str]: msgs = [] for path in self.error_info_map.keys(): if path not in self.flushed_files: - msgs.extend(self.file_messages(path)) + error_tuples = self.file_messages(path) + msgs.extend(self.format_messages(path, error_tuples)) return msgs def targets(self) -> set[str]: diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 170a883ce25d..fdda5f64284d 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -7626,3 +7626,13 @@ y = 1 class C: ... [out2] tmp/m.py:2: note: Revealed type is "def () -> other.C" + +[case testOutputFormatterIncremental] +# flags2: --output json +def wrong() -> int: + if wrong(): + return 0 +[out] +main:2: error: Missing return statement +[out2] +{"file": "main", "line": 2, "column": 0, "message": "Missing return statement", "hint": null, "code": "return", "severity": "error"} From 20aea0a6ca0710f5427239bdd2fd8e8bf1caf634 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sun, 14 Dec 2025 15:44:59 -0800 Subject: [PATCH 17/18] Update changelog for 1.19.1 (#20414) Also change the header for 1.18 because of https://github.com/python/mypy/issues/19910 --- CHANGELOG.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0be81310c6e1..ed5d947cb829 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -202,6 +202,17 @@ Related PRs: Please see [git log](https://github.com/python/typeshed/commits/main?after=ebce8d766b41fbf4d83cf47c1297563a9508ff60+0&branch=main&path=stdlib) for full list of standard library typeshed stub changes. +### Mypy 1.19.1 + +- Fix noncommutative joins with bounded TypeVars (Shantanu, PR [20345](https://github.com/python/mypy/pull/20345)) +- Respect output format for cached runs by serializing raw errors in cache metas (Ivan Levkivskyi, PR [20372](https://github.com/python/mypy/pull/20372)) +- Allow `types.NoneType` in match cases (A5rocks, PR [20383](https://github.com/python/mypy/pull/20383)) +- Fix mypyc generator regression with empty tuple (BobTheBuidler, PR [20371](https://github.com/python/mypy/pull/20371)) +- Fix crash involving Unpack-ed TypeVarTuple (Shantanu, PR [20323](https://github.com/python/mypy/pull/20323)) +- Fix crash on star import of redefinition (Ivan Levkivskyi, PR [20333](https://github.com/python/mypy/pull/20333)) +- Fix crash on typevar with forward ref used in other module (Ivan Levkivskyi, PR [20334](https://github.com/python/mypy/pull/20334)) +- Fail with an explicit error on PyPy (Ivan Levkivskyi, PR [20389](https://github.com/python/mypy/pull/20389)) + ### Acknowledgements Thanks to all mypy contributors who contributed to this release: @@ -237,7 +248,7 @@ Thanks to all mypy contributors who contributed to this release: I’d also like to thank my employer, Dropbox, for supporting mypy development. -## Mypy 1.18.1 +## Mypy 1.18 We’ve just uploaded mypy 1.18.1 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). Mypy is a static type checker for Python. This release includes new features, performance From 412c19a6bde31e7afa7f41afdf8356664689ae80 Mon Sep 17 00:00:00 2001 From: Shantanu Jain Date: Sun, 14 Dec 2025 15:46:31 -0800 Subject: [PATCH 18/18] Bump version to 1.19.1 --- mypy/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/version.py b/mypy/version.py index eef99f7203cf..eded284e7941 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.19.1+dev" +__version__ = "1.19.1" base_version = __version__ mypy_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))