diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e6f8c2cac38b..134d251d90b14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,9 @@ ## Next Release -## Mypy 1.18 +## Mypy 1.18.1 -We’ve just uploaded mypy 1.18 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). +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 improvements and bug fixes. You can install it as follows: @@ -14,7 +14,7 @@ You can read the full documentation for this release on [Read the Docs](http://m ### Mypy Performance Improvements -Mypy 1.18 includes numerous performance improvements, resulting in about 40% speedup +Mypy 1.18.1 includes numerous performance improvements, resulting in about 40% speedup compared to 1.17 when type checking mypy itself. In extreme cases, the improvement can be 10x or higher. The list below is an overview of the various mypy optimizations. Many mypyc improvements (discussed in a separate section below) also improve performance. @@ -283,6 +283,12 @@ Related PRs: Please see [git log](https://github.com/python/typeshed/commits/main?after=2480d7e7c74493a024eaf254c5d2c6f452c80ee2+0&branch=main&path=stdlib) for full list of standard library typeshed stub changes. +### Mypy 1.18.2 + +- Fix crash on recursive alias (Ivan Levkivskyi, PR [19845](https://github.com/python/mypy/pull/19845)) +- Add additional guidance for stubtest errors when runtime is `object.__init__` (Stephen Morton, PR [19733](https://github.com/python/mypy/pull/19733)) +- Fix handling of None values in f-string expressions in mypyc (BobTheBuidler, PR [19846](https://github.com/python/mypy/pull/19846)) + ### Acknowledgements Thanks to all mypy contributors who contributed to this release: diff --git a/mypy/indirection.py b/mypy/indirection.py index 06a158818fbed..8c24c7242c967 100644 --- a/mypy/indirection.py +++ b/mypy/indirection.py @@ -39,8 +39,9 @@ def find_modules(self, typs: Iterable[types.Type]) -> set[str]: def _visit(self, typ: types.Type) -> None: if isinstance(typ, types.TypeAliasType): # Avoid infinite recursion for recursive type aliases. - if typ not in self.seen_aliases: - self.seen_aliases.add(typ) + if typ in self.seen_aliases: + return + self.seen_aliases.add(typ) typ.accept(self) def _visit_type_tuple(self, typs: tuple[types.Type, ...]) -> None: diff --git a/mypy/stubtest.py b/mypy/stubtest.py index d4f96a3d9389f..4126f3959ee15 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -1053,7 +1053,10 @@ def get_kind(arg_name: str) -> nodes.ArgKind: def _verify_signature( - stub: Signature[nodes.Argument], runtime: Signature[inspect.Parameter], function_name: str + stub: Signature[nodes.Argument], + runtime: Signature[inspect.Parameter], + function_name: str, + warn_runtime_is_object_init: bool = False, ) -> Iterator[str]: # Check positional arguments match up for stub_arg, runtime_arg in zip(stub.pos, runtime.pos): @@ -1098,6 +1101,8 @@ def _verify_signature( msg = f'runtime does not have parameter "{stub_arg.variable.name}"' if runtime.varkw is not None: msg += ". Maybe you forgot to make it keyword-only in the stub?" + elif warn_runtime_is_object_init: + msg += ". You may need to write stubs for __new__ instead of __init__." yield msg else: yield f'stub parameter "{stub_arg.variable.name}" is not keyword-only' @@ -1137,7 +1142,11 @@ def _verify_signature( if arg not in {runtime_arg.name for runtime_arg in runtime.pos[len(stub.pos) :]}: yield f'runtime parameter "{arg}" is not keyword-only' else: - yield f'runtime does not have parameter "{arg}"' + msg = f'runtime does not have parameter "{arg}"' + if warn_runtime_is_object_init: + msg += ". You may need to write stubs for __new__ instead of __init__." + yield msg + for arg in sorted(set(runtime.kwonly) - set(stub.kwonly)): if arg in {stub_arg.variable.name for stub_arg in stub.pos}: # Don't report this if we've reported it before @@ -1223,7 +1232,12 @@ def verify_funcitem( if not signature: return - for message in _verify_signature(stub_sig, runtime_sig, function_name=stub.name): + for message in _verify_signature( + stub_sig, + runtime_sig, + function_name=stub.name, + warn_runtime_is_object_init=runtime is object.__init__, + ): yield Error( object_path, "is inconsistent, " + message, @@ -1333,7 +1347,12 @@ def verify_overloadedfuncdef( stub_sig = Signature.from_overloadedfuncdef(stub) runtime_sig = Signature.from_inspect_signature(signature) - for message in _verify_signature(stub_sig, runtime_sig, function_name=stub.name): + for message in _verify_signature( + stub_sig, + runtime_sig, + function_name=stub.name, + warn_runtime_is_object_init=runtime is object.__init__, + ): # TODO: This is a little hacky, but the addition here is super useful if "has a default value of type" in message: message += ( diff --git a/mypy/typeshed/stdlib/unittest/mock.pyi b/mypy/typeshed/stdlib/unittest/mock.pyi index f4b59e7cab906..f3e58bcd1c009 100644 --- a/mypy/typeshed/stdlib/unittest/mock.pyi +++ b/mypy/typeshed/stdlib/unittest/mock.pyi @@ -508,7 +508,8 @@ class MagicProxy(Base): def create_mock(self) -> Any: ... def __get__(self, obj: Any, _type: Any | None = None) -> Any: ... -class _ANY: +# See https://github.com/python/typeshed/issues/14701 +class _ANY(Any): def __eq__(self, other: object) -> Literal[True]: ... def __ne__(self, other: object) -> Literal[False]: ... __hash__: ClassVar[None] # type: ignore[assignment] diff --git a/mypy/version.py b/mypy/version.py index be15f5395fbee..fcfc04fcc40b6 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.18.1" +__version__ = "1.18.2" base_version = __version__ mypy_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 0880c62bc7a58..576b7a7ebffd8 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -719,7 +719,9 @@ def get_literal_str(expr: Expression) -> str | None: if isinstance(expr, StrExpr): return expr.value elif isinstance(expr, RefExpr) and isinstance(expr.node, Var) and expr.node.is_final: - return str(expr.node.final_value) + final_value = expr.node.final_value + if final_value is not None: + return str(final_value) return None for i in range(len(exprs) - 1): diff --git a/mypyc/test-data/run-strings.test b/mypyc/test-data/run-strings.test index 6960b0a043038..6a62db6ee3ee0 100644 --- a/mypyc/test-data/run-strings.test +++ b/mypyc/test-data/run-strings.test @@ -412,9 +412,16 @@ def test_basics() -> None: [case testFStrings] import decimal from datetime import datetime +from typing import Final var = 'mypyc' num = 20 +final_known_at_compile_time: Final = 'hello' + +def final_value_setter() -> str: + return 'goodbye' + +final_unknown_at_compile_time: Final = final_value_setter() def test_fstring_basics() -> None: assert f'Hello {var}, this is a test' == "Hello mypyc, this is a test" @@ -451,6 +458,8 @@ def test_fstring_basics() -> None: inf_num = float('inf') assert f'{nan_num}, {inf_num}' == 'nan, inf' + assert f'{final_known_at_compile_time} {final_unknown_at_compile_time}' == 'hello goodbye' + # F-strings would be translated into ''.join[string literals, format method call, ...] in mypy AST. # Currently we are using a str.join specializer for f-string speed up. We might not cover all cases # and the rest ones should fall back to a normal str.join method call. diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index defe7402730f0..1658e56f582bd 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -2577,6 +2577,13 @@ C(1)[0] [builtins fixtures/list.pyi] [out] +[case testSerializeRecursiveAlias] +from typing import Callable, Union + +Node = Union[str, int, Callable[[], "Node"]] +n: Node +[out] + [case testSerializeRecursiveAliases1] from typing import Type, Callable, Union