diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cc87cae5065..01d58ce6a1b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,32 +2,173 @@ ## Next Release +## Mypy 1.16 + +We’ve just uploaded mypy 1.16 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). +Mypy is a static type checker for Python. This release includes new features 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). + ### Different Property Getter and Setter Types -Mypy now supports using different types for property getter and setter. +Mypy now supports using different types for a property getter and setter: + ```python class A: - value: int + _value: int @property - def f(self) -> int: - return self.value - @f.setter - def f(self, x: str | int) -> None: + def foo(self) -> int: + return self._value + + @foo.setter + def foo(self, x: str | int) -> None: try: - self.value = int(x) + self._value = int(x) except ValueError: - raise Exception(f"'{x}' is not a valid value for 'f'") + raise Exception(f"'{x}' is not a valid value for 'foo'") ``` +This was contributed by Ivan Levkivskyi (PR [18510](https://github.com/python/mypy/pull/18510)). + +### Flexible Variable Redefinitions (Experimental) + +Mypy now allows unannotated variables to be freely redefined with +different types when using the experimental `--allow-redefinition-new` +flag. You will also need to enable `--local-partial-types`. Mypy will +now infer a union type when different types are assigned to a +variable: -Contributed by Ivan Levkivskyi (PR [18510](https://github.com/python/mypy/pull/18510)) +```py +# mypy: allow-redefinition-new, local-partial-types + +def f(n: int, b: bool) -> int | str: + if b: + x = n + else: + x = str(n) + # Type of 'x' is int | str here. + return x +``` + +Without the new flag, mypy only supports inferring optional types (`X +| None`) from multiple assignments, but now mypy can infer arbitrary +union types. + +An unannotated variable can now also have different types in different +code locations: + +```py +# mypy: allow-redefinition-new, local-partial-types +... + +if cond(): + for x in range(n): + # Type of 'x' is 'int' here + ... +else: + for x in ['a', 'b']: + # Type of 'x' is 'str' here + ... +``` + +We are planning to turn this flag on by default in mypy 2.0, along +with `--local-partial-types`. The feature is still experimental and +has known issues, and the semantics may still change in the +future. You may need to update or add type annotations when switching +to the new behavior, but if you encounter anything unexpected, please +create a GitHub issue. + +This was contributed by Jukka Lehtosalo +(PR [18727](https://github.com/python/mypy/pull/18727), PR [19153](https://github.com/python/mypy/pull/19153)). + +### Stricter Type Checking with Imprecise Types + +Mypy can now detect additional errors in code that uses `Any` types or has missing function annotations. + +When calling `dict.get(x, None)` on an object of type `dict[str, Any]`, this +now results in an optional type (in the past it was `Any`): + +```python +def f(d: dict[str, Any]) -> int: + # Error: Return value has type "Any | None" but expected "int" + return d.get("x", None) +``` + +Type narrowing using assignments can result in more precise types in +the presence of `Any` types: + +```python +def foo(): ... + +def bar(n: int) -> None: + x = foo() + # Type of 'x' is 'Any' here + if n > 5: + x = str(n) + # Type of 'x' is 'str' here +``` + +When using `--check-untyped-defs`, unannotated overrides are now +checked more strictly against superclass definitions. + +Related PRs: + + * Use union types instead of join in binder (Ivan Levkivskyi, PR [18538](https://github.com/python/mypy/pull/18538)) + * Check superclass compatibility of untyped methods if `--check-untyped-defs` is set (Stanislav Terliakov, PR [18970](https://github.com/python/mypy/pull/18970)) + +### Improvements to Attribute Resolution + +This release includes several fixes to inconsistent resolution of attribute, method and descriptor types. + + * Consolidate descriptor handling (Ivan Levkivskyi, PR [18831](https://github.com/python/mypy/pull/18831)) + * Make multiple inheritance checking use common semantics (Ivan Levkivskyi, PR [18876](https://github.com/python/mypy/pull/18876)) + * Make method override checking use common semantics (Ivan Levkivskyi, PR [18870](https://github.com/python/mypy/pull/18870)) + * Fix descriptor overload selection (Ivan Levkivskyi, PR [18868](https://github.com/python/mypy/pull/18868)) + * Handle union types when binding `self` (Ivan Levkivskyi, PR [18867](https://github.com/python/mypy/pull/18867)) + * Make variable override checking use common semantics (Ivan Levkivskyi, PR [18847](https://github.com/python/mypy/pull/18847)) + * Make descriptor handling behave consistently (Ivan Levkivskyi, PR [18831](https://github.com/python/mypy/pull/18831)) + +### Make Implementation for Abstract Overloads Optional + +The implementation can now be omitted for abstract overloaded methods, +even outside stubs: + +```py +from abc import abstractmethod +from typing import overload + +class C: + @abstractmethod + @overload + def foo(self, x: int) -> int: ... + + @abstractmethod + @overload + def foo(self, x: str) -> str: ... + + # No implementation required for "foo" +``` + +This was contributed by Ivan Levkivskyi (PR [18882](https://github.com/python/mypy/pull/18882)). + +### Option to Exclude Everything in .gitignore + +You can now use `--exclude-gitignore` to exclude everything in a +`.gitignore` file from the mypy build. This behaves similar to +excluding the paths using `--exclude`. We might enable this by default +in a future mypy release. + +This was contributed by Ivan Levkivskyi (PR [18696](https://github.com/python/mypy/pull/18696)). ### Selectively Disable Deprecated Warnings It's now possible to selectively disable warnings generated from [`warnings.deprecated`](https://docs.python.org/3/library/warnings.html#warnings.deprecated) using the [`--deprecated-calls-exclude`](https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-deprecated-calls-exclude) -option. +option: ```python # mypy --enable-error-code deprecated @@ -35,16 +176,269 @@ option. import foo foo.A().func() # OK, the deprecated warning is ignored +``` +```python # file foo.py + from typing_extensions import deprecated + class A: @deprecated("Use A.func2 instead") def func(self): pass + + ... ``` Contributed by Marc Mueller (PR [18641](https://github.com/python/mypy/pull/18641)) +### Annotating Native/Non-Native Classes in Mypyc + +You can now declare a class as a non-native class when compiling with +mypyc. Unlike native classes, which are extension classes and have an +immutable structure, non-native classes are normal Python classes at +runtime and are fully dynamic. Example: + +```python +from mypy_extensions import mypyc_attr + +@mypyc_attr(native_class=False) +class NonNativeClass: + ... + +o = NonNativeClass() + +# Ok, even if attribute "foo" not declared in class body +setattr(o, "foo", 1) +``` + +Classes are native by default in compiled modules, but classes that +use certain features (such as most metaclasses) are implicitly +non-native. + +You can also explicitly declare a class as native. In this case mypyc +will generate an error if it can't compile the class as a native +class, instead of falling back to a non-native class: + +```python +from mypy_extensions import mypyc_attr +from foo import MyMeta + +# Error: Unsupported metaclass for a native class +@mypyc_attr(native_class=True) +class C(metaclass=MyMeta): + ... +``` + +Since native classes are significantly more efficient that non-native +classes, you may want to ensure that certain classes always compiled +as native classes. + +This feature was contributed by Valentin Stanciu (PR [18802](https://github.com/python/mypy/pull/18802)). + +### Mypyc Fixes and Improvements + + * Improve documentation of native and non-native classes (Jukka Lehtosalo, PR [19154](https://github.com/python/mypy/pull/19154)) + * Fix compilation when using Python 3.13 debug build (Valentin Stanciu, PR [19045](https://github.com/python/mypy/pull/19045)) + * Show the reason why a class can't be a native class (Valentin Stanciu, PR [19016](https://github.com/python/mypy/pull/19016)) + * Support await/yield while temporary values are live (Michael J. Sullivan, PR [16305](https://github.com/python/mypy/pull/16305)) + * Fix spilling values with overlapping error values (Jukka Lehtosalo, PR [18961](https://github.com/python/mypy/pull/18961)) + * Fix reference count of spilled register in async def (Jukka Lehtosalo, PR [18957](https://github.com/python/mypy/pull/18957)) + * Add basic optimization for `sorted` (Marc Mueller, PR [18902](https://github.com/python/mypy/pull/18902)) + * Fix access of class object in a type annotation (Advait Dixit, PR [18874](https://github.com/python/mypy/pull/18874)) + * Optimize `list.__imul__` and `tuple.__mul__ `(Marc Mueller, PR [18887](https://github.com/python/mypy/pull/18887)) + * Optimize `list.__add__`, `list.__iadd__` and `tuple.__add__` (Marc Mueller, PR [18845](https://github.com/python/mypy/pull/18845)) + * Add and implement primitive `list.copy()` (exertustfm, PR [18771](https://github.com/python/mypy/pull/18771)) + * Optimize `builtins.repr` (Marc Mueller, PR [18844](https://github.com/python/mypy/pull/18844)) + * Support iterating over keys/values/items of dict-bound TypeVar and ParamSpec.kwargs (Stanislav Terliakov, PR [18789](https://github.com/python/mypy/pull/18789)) + * Add efficient primitives for `str.strip()` etc. (Advait Dixit, PR [18742](https://github.com/python/mypy/pull/18742)) + * Document that `strip()` etc. are optimized (Jukka Lehtosalo, PR [18793](https://github.com/python/mypy/pull/18793)) + * Fix mypyc crash with enum type aliases (Valentin Stanciu, PR [18725](https://github.com/python/mypy/pull/18725)) + * Optimize `str.find` and `str.rfind` (Marc Mueller, PR [18709](https://github.com/python/mypy/pull/18709)) + * Optimize `str.__contains__` (Marc Mueller, PR [18705](https://github.com/python/mypy/pull/18705)) + * Fix order of steal/unborrow in tuple unpacking (Ivan Levkivskyi, PR [18732](https://github.com/python/mypy/pull/18732)) + * Optimize `str.partition` and `str.rpartition` (Marc Mueller, PR [18702](https://github.com/python/mypy/pull/18702)) + * Optimize `str.startswith` and `str.endswith` with tuple argument (Marc Mueller, PR [18678](https://github.com/python/mypy/pull/18678)) + * Improve `str.startswith` and `str.endswith` with tuple argument (Marc Mueller, PR [18703](https://github.com/python/mypy/pull/18703)) + * `pythoncapi_compat`: don't define Py_NULL if it is already defined (Michael R. Crusoe, PR [18699](https://github.com/python/mypy/pull/18699)) + * Optimize `str.splitlines` (Marc Mueller, PR [18677](https://github.com/python/mypy/pull/18677)) + * Mark `dict.setdefault` as optimized (Marc Mueller, PR [18685](https://github.com/python/mypy/pull/18685)) + * Support `__del__` methods (Advait Dixit, PR [18519](https://github.com/python/mypy/pull/18519)) + * Optimize `str.rsplit` (Marc Mueller, PR [18673](https://github.com/python/mypy/pull/18673)) + * Optimize `str.removeprefix` and `str.removesuffix` (Marc Mueller, PR [18672](https://github.com/python/mypy/pull/18672)) + * Recognize literal types in `__match_args__` (Stanislav Terliakov, PR [18636](https://github.com/python/mypy/pull/18636)) + * Fix non extension classes with attribute annotations using forward references (Valentin Stanciu, PR [18577](https://github.com/python/mypy/pull/18577)) + * Use lower-case generic types such as `list[t]` in documentation (Jukka Lehtosalo, PR [18576](https://github.com/python/mypy/pull/18576)) + * Improve support for `frozenset` (Marc Mueller, PR [18571](https://github.com/python/mypy/pull/18571)) + * Fix wheel build for cp313-win (Marc Mueller, PR [18560](https://github.com/python/mypy/pull/18560)) + * Reduce impact of immortality (introduced in Python 3.12) on reference counting performance (Jukka Lehtosalo, PR [18459](https://github.com/python/mypy/pull/18459)) + * Update math error messages for 3.14 (Marc Mueller, PR [18534](https://github.com/python/mypy/pull/18534)) + * Update math error messages for 3.14 (2) (Marc Mueller, PR [18949](https://github.com/python/mypy/pull/18949)) + * Replace deprecated `_PyLong_new` with `PyLongWriter` API (Marc Mueller, PR [18532](https://github.com/python/mypy/pull/18532)) + +### Fixes to Crashes + + * Traverse module ancestors when traversing reachable graph nodes during dmypy update (Stanislav Terliakov, PR [18906](https://github.com/python/mypy/pull/18906)) + * Fix crash on multiple unpacks in a bare type application (Stanislav Terliakov, PR [18857](https://github.com/python/mypy/pull/18857)) + * Prevent crash when enum/TypedDict call is stored as a class attribute (Stanislav Terliakov, PR [18861](https://github.com/python/mypy/pull/18861)) + * Fix crash on multiple unpacks in a bare type application (Stanislav Terliakov, PR [18857](https://github.com/python/mypy/pull/18857)) + * Fix crash on type inference against non-normal callables (Ivan Levkivskyi, PR [18858](https://github.com/python/mypy/pull/18858)) + * Fix crash on decorated getter in settable property (Ivan Levkivskyi, PR [18787](https://github.com/python/mypy/pull/18787)) + * Fix crash on callable with `*args` and suffix against Any (Ivan Levkivskyi, PR [18781](https://github.com/python/mypy/pull/18781)) + * Fix crash on deferred supertype and setter override (Ivan Levkivskyi, PR [18649](https://github.com/python/mypy/pull/18649)) + * Fix crashes on incorrectly detected recursive aliases (Ivan Levkivskyi, PR [18625](https://github.com/python/mypy/pull/18625)) + * Report that `NamedTuple` and `dataclass` are incompatile instead of crashing (Christoph Tyralla, PR [18633](https://github.com/python/mypy/pull/18633)) + * Fix mypy daemon crash (Valentin Stanciu, PR [19087](https://github.com/python/mypy/pull/19087)) + +### Performance Improvements + +These are specific to mypy. Mypyc-related performance improvements are discussed elsewhere. + + * Speed up binding `self` in trivial cases (Ivan Levkivskyi, PR [19024](https://github.com/python/mypy/pull/19024)) + * Small constraint solver optimization (Aaron Gokaslan, PR [18688](https://github.com/python/mypy/pull/18688)) + +### Documentation Updates + + * Improve documentation of `--strict` (lenayoung8, PR [18903](https://github.com/python/mypy/pull/18903)) + * Remove a note about `from __future__ import annotations` (Ageev Maxim, PR [18915](https://github.com/python/mypy/pull/18915)) + * Improve documentation on type narrowing (Tim Hoffmann, PR [18767](https://github.com/python/mypy/pull/18767)) + * Fix metaclass usage example (Georg, PR [18686](https://github.com/python/mypy/pull/18686)) + * Update documentation on `extra_checks` flag (Ivan Levkivskyi, PR [18537](https://github.com/python/mypy/pull/18537)) + +### Stubgen Improvements + + * Fix `TypeAlias` handling (Alexey Makridenko, PR [18960](https://github.com/python/mypy/pull/18960)) + * Handle `arg=None` in C extension modules (Anthony Sottile, PR [18768](https://github.com/python/mypy/pull/18768)) + * Fix valid type detection to allow pipe unions (Chad Dombrova, PR [18726](https://github.com/python/mypy/pull/18726)) + * Include simple decorators in stub files (Marc Mueller, PR [18489](https://github.com/python/mypy/pull/18489)) + * Support positional and keyword-only arguments in stubdoc (Paul Ganssle, PR [18762](https://github.com/python/mypy/pull/18762)) + * Fall back to `Incomplete` if we are unable to determine the module name (Stanislav Terliakov, PR [19084](https://github.com/python/mypy/pull/19084)) + +### Stubtest Improvements + + * Make stubtest ignore `__slotnames__` (Nick Pope, PR [19077](https://github.com/python/mypy/pull/19077)) + * Fix stubtest tests on 3.14 (Jelle Zijlstra, PR [19074](https://github.com/python/mypy/pull/19074)) + * Support for `strict_bytes` in stubtest (Joren Hammudoglu, PR [19002](https://github.com/python/mypy/pull/19002)) + * Understand override (Shantanu, PR [18815](https://github.com/python/mypy/pull/18815)) + * Better checking of runtime arguments with dunder names (Shantanu, PR [18756](https://github.com/python/mypy/pull/18756)) + * Ignore setattr and delattr inherited from object (Stephen Morton, PR [18325](https://github.com/python/mypy/pull/18325)) + +### Miscellaneous Fixes and Improvements + + * Add `--strict-bytes` to `--strict` (wyattscarpenter, PR [19049](https://github.com/python/mypy/pull/19049)) + * Admit that Final variables are never redefined (Stanislav Terliakov, PR [19083](https://github.com/python/mypy/pull/19083)) + * Add special support for `@django.cached_property` needed in `django-stubs` (sobolevn, PR [18959](https://github.com/python/mypy/pull/18959)) + * Do not narrow types to `Never` with binder (Ivan Levkivskyi, PR [18972](https://github.com/python/mypy/pull/18972)) + * Local forward references should precede global forward references (Ivan Levkivskyi, PR [19000](https://github.com/python/mypy/pull/19000)) + * Do not cache module lookup results in incremental mode that may become invalid (Stanislav Terliakov, PR [19044](https://github.com/python/mypy/pull/19044)) + * Only consider meta variables in ambiguous "any of" constraints (Stanislav Terliakov, PR [18986](https://github.com/python/mypy/pull/18986)) + * Allow accessing `__init__` on final classes and when `__init__` is final (Stanislav Terliakov, PR [19035](https://github.com/python/mypy/pull/19035)) + * Treat varargs as positional-only (A5rocks, PR [19022](https://github.com/python/mypy/pull/19022)) + * Enable colored output for argparse help in Python 3.14 (Marc Mueller, PR [19021](https://github.com/python/mypy/pull/19021)) + * Fix argparse for Python 3.14 (Marc Mueller, PR [19020](https://github.com/python/mypy/pull/19020)) + * `dmypy suggest` can now suggest through contextmanager-based decorators (Anthony Sottile, PR [18948](https://github.com/python/mypy/pull/18948)) + * Fix `__r__` being used under the same `____` hook (Arnav Jain, PR [18995](https://github.com/python/mypy/pull/18995)) + * Prioritize `.pyi` from `-stubs` packages over bundled `.pyi` (Joren Hammudoglu, PR [19001](https://github.com/python/mypy/pull/19001)) + * Fix missing subtype check case for `type[T]` (Stanislav Terliakov, PR [18975](https://github.com/python/mypy/pull/18975)) + * Fixes to the detection of redundant casts (Anthony Sottile, PR [18588](https://github.com/python/mypy/pull/18588)) + * Make some parse errors non-blocking (Shantanu, PR [18941](https://github.com/python/mypy/pull/18941)) + * Fix PEP 695 type alias with a mix of type arguments (PEP 696) (Marc Mueller, PR [18919](https://github.com/python/mypy/pull/18919)) + * Allow deeper recursion in mypy daemon, better error reporting (Carter Dodd, PR [17707](https://github.com/python/mypy/pull/17707)) + * Fix swapped errors for frozen/non-frozen dataclass inheritance (Nazrawi Demeke, PR [18918](https://github.com/python/mypy/pull/18918)) + * Fix incremental issue with namespace packages (Shantanu, PR [18907](https://github.com/python/mypy/pull/18907)) + * Exclude irrelevant members when narrowing union overlapping with enum (Stanislav Terliakov, PR [18897](https://github.com/python/mypy/pull/18897)) + * Flatten union before contracting literals when checking subtyping (Stanislav Terliakov, PR [18898](https://github.com/python/mypy/pull/18898)) + * Do not add `kw_only` dataclass fields to `__match_args__` (sobolevn, PR [18892](https://github.com/python/mypy/pull/18892)) + * Fix error message when returning long tuple with type mismatch (Thomas Mattone, PR [18881](https://github.com/python/mypy/pull/18881)) + * Treat `TypedDict` (old-style) aliases as regular `TypedDict`s (Stanislav Terliakov, PR [18852](https://github.com/python/mypy/pull/18852)) + * Warn about unused `type: ignore` comments when error code is disabled (Brian Schubert, PR [18849](https://github.com/python/mypy/pull/18849)) + * Reject duplicate `ParamSpec.{args,kwargs}` at call site (Stanislav Terliakov, PR [18854](https://github.com/python/mypy/pull/18854)) + * Make detection of enum members more consistent (sobolevn, PR [18675](https://github.com/python/mypy/pull/18675)) + * Admit that `**kwargs` mapping subtypes may have no direct type parameters (Stanislav Terliakov, PR [18850](https://github.com/python/mypy/pull/18850)) + * Don't suggest `types-setuptools` for `pkg_resources` (Shantanu, PR [18840](https://github.com/python/mypy/pull/18840)) + * Suggest `scipy-stubs` for `scipy` as non-typeshed stub package (Joren Hammudoglu, PR [18832](https://github.com/python/mypy/pull/18832)) + * Narrow tagged unions in match statements (Gene Parmesan Thomas, PR [18791](https://github.com/python/mypy/pull/18791)) + * Consistently store settable property type (Ivan Levkivskyi, PR [18774](https://github.com/python/mypy/pull/18774)) + * Do not blindly undefer on leaving function (Ivan Levkivskyi, PR [18674](https://github.com/python/mypy/pull/18674)) + * Process superclass methods before subclass methods in semanal (Ivan Levkivskyi, PR [18723](https://github.com/python/mypy/pull/18723)) + * Only defer top-level functions (Ivan Levkivskyi, PR [18718](https://github.com/python/mypy/pull/18718)) + * Add one more type-checking pass (Ivan Levkivskyi, PR [18717](https://github.com/python/mypy/pull/18717)) + * Properly account for `member` and `nonmember` in enums (sobolevn, PR [18559](https://github.com/python/mypy/pull/18559)) + * Fix instance vs tuple subtyping edge case (Ivan Levkivskyi, PR [18664](https://github.com/python/mypy/pull/18664)) + * Improve handling of Any/object in variadic generics (Ivan Levkivskyi, PR [18643](https://github.com/python/mypy/pull/18643)) + * Fix handling of named tuples in class match pattern (Ivan Levkivskyi, PR [18663](https://github.com/python/mypy/pull/18663)) + * Fix regression for user config files (Shantanu, PR [18656](https://github.com/python/mypy/pull/18656)) + * Fix dmypy socket issue on GNU/Hurd (Mattias Ellert, PR [18630](https://github.com/python/mypy/pull/18630)) + * Don't assume that for loop body index variable is always set (Jukka Lehtosalo, PR [18631](https://github.com/python/mypy/pull/18631)) + * Fix overlap check for variadic generics (Ivan Levkivskyi, PR [18638](https://github.com/python/mypy/pull/18638)) + * Improve support for `functools.partial` of overloaded callable protocol (Shantanu, PR [18639](https://github.com/python/mypy/pull/18639)) + * Allow lambdas in `except*` clauses (Stanislav Terliakov, PR [18620](https://github.com/python/mypy/pull/18620)) + * Fix trailing commas in many multiline string options in `pyproject.toml` (sobolevn, PR [18624](https://github.com/python/mypy/pull/18624)) + * Allow trailing commas for `files` setting in `mypy.ini` and `setup.ini` (sobolevn, PR [18621](https://github.com/python/mypy/pull/18621)) + * Fix "not callable" issue for `@dataclass(frozen=True)` with `Final` attr (sobolevn, PR [18572](https://github.com/python/mypy/pull/18572)) + * Add missing TypedDict special case when checking member access (Stanislav Terliakov, PR [18604](https://github.com/python/mypy/pull/18604)) + * Use lower case `list` and `dict` in invariance notes (Jukka Lehtosalo, PR [18594](https://github.com/python/mypy/pull/18594)) + * Fix inference when class and instance match protocol (Ivan Levkivskyi, PR [18587](https://github.com/python/mypy/pull/18587)) + * Remove support for `builtins.Any` (Marc Mueller, PR [18578](https://github.com/python/mypy/pull/18578)) + * Update the overlapping check for tuples to account for NamedTuples (A5rocks, PR [18564](https://github.com/python/mypy/pull/18564)) + * Fix `@deprecated` (PEP 702) with normal overloaded methods (Christoph Tyralla, PR [18477](https://github.com/python/mypy/pull/18477)) + * Start propagating end columns/lines for `type-arg` errors (A5rocks, PR [18533](https://github.com/python/mypy/pull/18533)) + * Improve handling of `type(x) is Foo` checks (Stanislav Terliakov, PR [18486](https://github.com/python/mypy/pull/18486)) + * Suggest `typing.Literal` for exit-return error messages (Marc Mueller, PR [18541](https://github.com/python/mypy/pull/18541)) + * Allow redefinitions in except/else/finally (Stanislav Terliakov, PR [18515](https://github.com/python/mypy/pull/18515)) + * Disallow setting Python version using inline config (Shantanu, PR [18497](https://github.com/python/mypy/pull/18497)) + * Improve type inference in tuple multiplication plugin (Shantanu, PR [18521](https://github.com/python/mypy/pull/18521)) + * Add missing line number to `yield from` with wrong type (Stanislav Terliakov, PR [18518](https://github.com/python/mypy/pull/18518)) + * Hint at argument names when formatting callables with compatible return types in error messages (Stanislav Terliakov, PR [18495](https://github.com/python/mypy/pull/18495)) + * Add better naming and improve compatibility for ad hoc intersections of instances (Christoph Tyralla, PR [18506](https://github.com/python/mypy/pull/18506)) + +### Acknowledgements + +Thanks to all mypy contributors who contributed to this release: + +- A5rocks +- Aaron Gokaslan +- Advait Dixit +- Ageev Maxim +- Alexey Makridenko +- Ali Hamdan +- Anthony Sottile +- Arnav Jain +- Brian Schubert +- bzoracler +- Carter Dodd +- Chad Dombrova +- Christoph Tyralla +- Dimitri Papadopoulos Orfanos +- Emma Smith +- exertustfm +- Gene Parmesan Thomas +- Georg +- Ivan Levkivskyi +- Jared Hance +- Jelle Zijlstra +- Joren Hammudoglu +- lenayoung8 +- Marc Mueller +- Mattias Ellert +- Michael J. Sullivan +- Michael R. Crusoe +- Nazrawi Demeke +- Nick Pope +- Paul Ganssle +- Shantanu +- sobolevn +- Stanislav Terliakov +- Stephen Morton +- Thomas Mattone +- Tim Hoffmann +- Tim Ruffing +- Valentin Stanciu +- Wesley Collin Wright +- wyattscarpenter + +I’d also like to thank my employer, Dropbox, for supporting mypy development. + ## Mypy 1.15 We’ve just uploaded mypy 1.15 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). @@ -408,6 +802,7 @@ This was contributed by Marc Mueller (PR [18014](https://github.com/python/mypy/ ### Other Notables Fixes and Improvements + * Allow enum members to have type objects as values (Jukka Lehtosalo, PR [19160](https://github.com/python/mypy/pull/19160)) * Show `Protocol` `__call__` for arguments with incompatible types (MechanicalConstruct, PR [18214](https://github.com/python/mypy/pull/18214)) * Make join and meet symmetric with `strict_optional` (MechanicalConstruct, PR [18227](https://github.com/python/mypy/pull/18227)) * Preserve block unreachablility when checking function definitions with constrained TypeVars (Brian Schubert, PR [18217](https://github.com/python/mypy/pull/18217)) diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index b455e287017e..dfed280d12ed 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -593,12 +593,58 @@ of the above sections. This flag causes mypy to suppress errors caused by not being able to fully infer the types of global and class variables. -.. option:: --allow-redefinition +.. option:: --allow-redefinition-new By default, mypy won't allow a variable to be redefined with an - unrelated type. This flag enables redefinition of a variable with an + unrelated type. This *experimental* flag enables the redefinition of + unannotated variables with an arbitrary type. You will also need to enable + :option:`--local-partial-types `. + Example: + + .. code-block:: python + + def maybe_convert(n: int, b: bool) -> int | str: + if b: + x = str(n) # Assign "str" + else: + x = n # Assign "int" + # Type of "x" is "int | str" here. + return x + + Without the new flag, mypy only supports inferring optional types + (``X | None``) from multiple assignments. With this option enabled, + mypy can infer arbitrary union types. + + This also enables an unannotated variable to have different types in different + code locations: + + .. code-block:: python + + if check(): + for x in range(n): + # Type of "x" is "int" here. + ... + else: + for x in ['a', 'b']: + # Type of "x" is "str" here. + ... + + Note: We are planning to turn this flag on by default in a future mypy + release, along with :option:`--local-partial-types `. + The feature is still experimental, and the semantics may still change. + +.. option:: --allow-redefinition + + This is an older variant of + :option:`--allow-redefinition-new `. + This flag enables redefinition of a variable with an arbitrary type *in some contexts*: only redefinitions within the same block and nesting depth as the original definition are allowed. + + We have no plans to remove this flag, but we expect that + :option:`--allow-redefinition-new ` + will replace this flag for new use cases eventually. + Example where this can be useful: .. code-block:: python diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index de51f0c796fd..9f23617b9481 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -713,6 +713,44 @@ section of the command line docs. Causes mypy to suppress errors caused by not being able to fully infer the types of global and class variables. +.. confval:: allow_redefinition_new + + :type: boolean + :default: False + + By default, mypy won't allow a variable to be redefined with an + unrelated type. This *experimental* flag enables the redefinition of + unannotated variables with an arbitrary type. You will also need to enable + :confval:`local_partial_types`. + Example: + + .. code-block:: python + + def maybe_convert(n: int, b: bool) -> int | str: + if b: + x = str(n) # Assign "str" + else: + x = n # Assign "int" + # Type of "x" is "int | str" here. + return x + + This also enables an unannotated variable to have different types in different + code locations: + + .. code-block:: python + + if check(): + for x in range(n): + # Type of "x" is "int" here. + ... + else: + for x in ['a', 'b']: + # Type of "x" is "str" here. + ... + + Note: We are planning to turn this flag on by default in a future mypy + release, along with :confval:`local_partial_types`. + .. confval:: allow_redefinition :type: boolean @@ -746,6 +784,7 @@ section of the command line docs. Disallows inferring variable type for ``None`` from two assignments in different scopes. This is always implicitly enabled when using the :ref:`mypy daemon `. + This will be enabled by default in a future mypy release. .. confval:: disable_error_code diff --git a/mypy/checker.py b/mypy/checker.py index aceb0291926a..dc3d670dd73e 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -675,11 +675,9 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: assert isinstance(defn.items[0], Decorator) self.visit_decorator(defn.items[0]) if defn.items[0].var.is_settable_property: - # TODO: here and elsewhere we assume setter immediately follows getter. - assert isinstance(defn.items[1], Decorator) # Perform a reduced visit just to infer the actual setter type. - self.visit_decorator_inner(defn.items[1], skip_first_item=True) - setter_type = defn.items[1].var.type + self.visit_decorator_inner(defn.setter, skip_first_item=True) + setter_type = defn.setter.var.type # Check if the setter can accept two positional arguments. any_type = AnyType(TypeOfAny.special_form) fallback_setter_type = CallableType( @@ -690,7 +688,7 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: fallback=self.named_type("builtins.function"), ) if setter_type and not is_subtype(setter_type, fallback_setter_type): - self.fail("Invalid property setter signature", defn.items[1].func) + self.fail("Invalid property setter signature", defn.setter.func) setter_type = self.extract_callable_type(setter_type, defn) if not isinstance(setter_type, CallableType) or len(setter_type.arg_types) != 2: # TODO: keep precise type for callables with tricky but valid signatures. @@ -2149,7 +2147,7 @@ def check_setter_type_override(self, defn: OverloadedFuncDef, base: TypeInfo) -> assert typ is not None and original_type is not None if not is_subtype(original_type, typ): - self.msg.incompatible_setter_override(defn.items[1], typ, original_type, base) + self.msg.incompatible_setter_override(defn.setter, typ, original_type, base) def check_method_override_for_base_with_name( self, defn: FuncDef | OverloadedFuncDef | Decorator, name: str, base: TypeInfo @@ -2448,7 +2446,7 @@ def erase_override(t: Type) -> Type: if not is_subtype(original_arg_type, erase_override(override_arg_type)): context: Context = node if isinstance(node, FuncDef) and not node.is_property: - arg_node = node.arguments[i + len(override.bound_args)] + arg_node = node.arguments[i + override.bound()] if arg_node.line != -1: context = arg_node self.msg.argument_incompatible_with_supertype( @@ -3426,7 +3424,9 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, rvalue: Expression) -> # store the rvalue type on the variable. actual_lvalue_type = None if lvalue_node.is_inferred and not lvalue_node.explicit_self_type: - rvalue_type = self.expr_checker.accept(rvalue, lvalue_node.type) + # Don't use partial types as context, similar to regular code path. + ctx = lvalue_node.type if not isinstance(lvalue_node.type, PartialType) else None + rvalue_type = self.expr_checker.accept(rvalue, ctx) actual_lvalue_type = lvalue_node.type lvalue_node.type = rvalue_type lvalue_type, _ = self.node_type_from_base(lvalue_node.name, lvalue_node.info, lvalue) @@ -6160,31 +6160,15 @@ def find_isinstance_check_helper( # considered "always right" (i.e. even if the types are not overlapping). # Also note that a care must be taken to unwrap this back at read places # where we use this to narrow down declared type. - with self.msg.filter_errors(), self.local_type_map(): - # `node.callee` can be an `overload`ed function, - # we need to resolve the real `overload` case. - _, real_func = self.expr_checker.check_call( - get_proper_type(self.lookup_type(node.callee)), - node.args, - node.arg_kinds, - node, - node.arg_names, - ) - real_func = get_proper_type(real_func) - if not isinstance(real_func, CallableType) or not ( - real_func.type_guard or real_func.type_is - ): - return {}, {} - - if real_func.type_guard is not None: - return {expr: TypeGuardedType(real_func.type_guard)}, {} + if node.callee.type_guard is not None: + return {expr: TypeGuardedType(node.callee.type_guard)}, {} else: - assert real_func.type_is is not None + assert node.callee.type_is is not None return conditional_types_to_typemaps( expr, *self.conditional_types_with_intersection( self.lookup_type(expr), - [TypeRange(real_func.type_is, is_upper_bound=False)], + [TypeRange(node.callee.type_is, is_upper_bound=False)], expr, ), ) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index ba2d38b6f528..df0e9bdaedde 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2920,37 +2920,16 @@ def infer_overload_return_type( elif all_same_types([erase_type(typ) for typ in return_types]): self.chk.store_types(type_maps[0]) return erase_type(return_types[0]), erase_type(inferred_types[0]) - return self.check_call( - callee=AnyType(TypeOfAny.special_form), - args=args, - arg_kinds=arg_kinds, - arg_names=arg_names, - context=context, - callable_name=callable_name, - object_type=object_type, - ) - elif not all_same_type_narrowers(matches): - # This is an example of how overloads can be: - # - # @overload - # def is_int(obj: float) -> TypeGuard[float]: ... - # @overload - # def is_int(obj: int) -> TypeGuard[int]: ... - # - # x: Any - # if is_int(x): - # reveal_type(x) # N: int | float - # - # So, we need to check that special case. - return self.check_call( - callee=self.combine_function_signatures(cast("list[ProperType]", matches)), - args=args, - arg_kinds=arg_kinds, - arg_names=arg_names, - context=context, - callable_name=callable_name, - object_type=object_type, - ) + else: + return self.check_call( + callee=AnyType(TypeOfAny.special_form), + args=args, + arg_kinds=arg_kinds, + arg_names=arg_names, + context=context, + callable_name=callable_name, + object_type=object_type, + ) else: # Success! No ambiguity; return the first match. self.chk.store_types(type_maps[0]) @@ -3165,8 +3144,6 @@ def combine_function_signatures(self, types: list[ProperType]) -> AnyType | Call new_args: list[list[Type]] = [[] for _ in range(len(callables[0].arg_types))] new_kinds = list(callables[0].arg_kinds) new_returns: list[Type] = [] - new_type_guards: list[Type] = [] - new_type_narrowers: list[Type] = [] too_complex = False for target in callables: @@ -3193,25 +3170,8 @@ def combine_function_signatures(self, types: list[ProperType]) -> AnyType | Call for i, arg in enumerate(target.arg_types): new_args[i].append(arg) new_returns.append(target.ret_type) - if target.type_guard: - new_type_guards.append(target.type_guard) - if target.type_is: - new_type_narrowers.append(target.type_is) - - if new_type_guards and new_type_narrowers: - # They cannot be defined at the same time, - # declaring this function as too complex! - too_complex = True - union_type_guard = None - union_type_is = None - else: - union_type_guard = make_simplified_union(new_type_guards) if new_type_guards else None - union_type_is = ( - make_simplified_union(new_type_narrowers) if new_type_narrowers else None - ) union_return = make_simplified_union(new_returns) - if too_complex: any = AnyType(TypeOfAny.special_form) return callables[0].copy_modified( @@ -3221,8 +3181,6 @@ def combine_function_signatures(self, types: list[ProperType]) -> AnyType | Call ret_type=union_return, variables=variables, implicit=True, - type_guard=union_type_guard, - type_is=union_type_is, ) final_args = [] @@ -3236,8 +3194,6 @@ def combine_function_signatures(self, types: list[ProperType]) -> AnyType | Call ret_type=union_return, variables=variables, implicit=True, - type_guard=union_type_guard, - type_is=union_type_is, ) def erased_signature_similarity( @@ -5014,7 +4970,7 @@ def apply_type_arguments_to_callable( tp.fallback, name="tuple", definition=tp.definition, - bound_args=tp.bound_args, + is_bound=tp.is_bound, ) self.msg.incompatible_type_application( min_arg_count, len(type_vars), len(args), ctx @@ -6594,25 +6550,6 @@ def all_same_types(types: list[Type]) -> bool: return all(is_same_type(t, types[0]) for t in types[1:]) -def all_same_type_narrowers(types: list[CallableType]) -> bool: - if len(types) <= 1: - return True - - type_guards: list[Type] = [] - type_narrowers: list[Type] = [] - - for typ in types: - if typ.type_guard: - type_guards.append(typ.type_guard) - if typ.type_is: - type_narrowers.append(typ.type_is) - if type_guards and type_narrowers: - # Some overloads declare `TypeGuard` and some declare `TypeIs`, - # we cannot handle this in a union. - return False - return all_same_types(type_guards) and all_same_types(type_narrowers) - - def merge_typevars_in_callables_by_name( callables: Sequence[CallableType], ) -> tuple[list[CallableType], list[TypeVarType]]: diff --git a/mypy/checkmember.py b/mypy/checkmember.py index cc104fed0752..596eccab09a7 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -341,8 +341,8 @@ def analyze_instance_member_access( assert isinstance(method, OverloadedFuncDef) getter = method.items[0] assert isinstance(getter, Decorator) - if mx.is_lvalue and (len(items := method.items) > 1): - mx.chk.warn_deprecated(items[1], mx.context) + if mx.is_lvalue and getter.var.is_settable_property: + mx.chk.warn_deprecated(method.setter, mx.context) return analyze_var(name, getter.var, typ, mx) if mx.is_lvalue and not mx.suppress_errors: @@ -916,7 +916,7 @@ def analyze_var( bound_items = [] for ct in call_type.items if isinstance(call_type, UnionType) else [call_type]: p_ct = get_proper_type(ct) - if isinstance(p_ct, FunctionLike) and not p_ct.is_type_obj(): + if isinstance(p_ct, FunctionLike) and (not p_ct.bound() or var.is_property): item = expand_and_bind_callable(p_ct, var, itype, name, mx, is_trivial_self) else: item = expand_without_binding(ct, var, itype, original_itype, mx) @@ -974,6 +974,10 @@ def expand_and_bind_callable( assert isinstance(expanded, CallableType) if var.is_settable_property and mx.is_lvalue and var.setter_type is not None: # TODO: use check_call() to infer better type, same as for __set__(). + if not expanded.arg_types: + # This can happen when accessing invalid property from its own body, + # error will be reported elsewhere. + return AnyType(TypeOfAny.from_error) return expanded.arg_types[0] else: return expanded.ret_type @@ -1499,6 +1503,6 @@ def bind_self_fast(method: F, original_type: Type | None = None) -> F: arg_types=method.arg_types[1:], arg_kinds=method.arg_kinds[1:], arg_names=method.arg_names[1:], - bound_args=[original_type], + is_bound=True, ) return cast(F, res) diff --git a/mypy/constraints.py b/mypy/constraints.py index 8e7a30e05ffb..73b7ec7954a4 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -1066,8 +1066,8 @@ def infer_constraints_from_protocol_members( inst, erase_typevars(temp), ignore_pos_arg_names=True ): continue - # This exception matches the one in subtypes.py, see PR #14121 for context. - if member == "__call__" and instance.type.is_metaclass(): + # This exception matches the one in typeops.py, see PR #14121 for context. + if member == "__call__" and instance.type.is_metaclass(precise=True): continue res.extend(infer_constraints(temp, inst, self.direction)) if mypy.subtypes.IS_SETTABLE in mypy.subtypes.get_member_flags(member, protocol): diff --git a/mypy/fixup.py b/mypy/fixup.py index 8e7cd40544bf..0e9c186fd42a 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -271,9 +271,6 @@ def visit_callable_type(self, ct: CallableType) -> None: ct.ret_type.accept(self) for v in ct.variables: v.accept(self) - for arg in ct.bound_args: - if arg: - arg.accept(self) if ct.type_guard is not None: ct.type_guard.accept(self) if ct.type_is is not None: diff --git a/mypy/messages.py b/mypy/messages.py index 2e07d7f63498..47e7d20dde78 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -644,8 +644,8 @@ def incompatible_argument( callee_name = callable_name(callee) if callee_name is not None: name = callee_name - if callee.bound_args and callee.bound_args[0] is not None: - base = format_type(callee.bound_args[0], self.options) + if object_type is not None: + base = format_type(object_type, self.options) else: base = extract_type(name) diff --git a/mypy/nodes.py b/mypy/nodes.py index 584e56667944..0d4c96ac5793 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -550,12 +550,20 @@ class OverloadedFuncDef(FuncBase, SymbolNode, Statement): Overloaded variants must be consecutive in the source file. """ - __slots__ = ("items", "unanalyzed_items", "impl", "deprecated", "_is_trivial_self") + __slots__ = ( + "items", + "unanalyzed_items", + "impl", + "deprecated", + "setter_index", + "_is_trivial_self", + ) items: list[OverloadPart] unanalyzed_items: list[OverloadPart] impl: OverloadPart | None deprecated: str | None + setter_index: int | None def __init__(self, items: list[OverloadPart]) -> None: super().__init__() @@ -563,6 +571,7 @@ def __init__(self, items: list[OverloadPart]) -> None: self.unanalyzed_items = items.copy() self.impl = None self.deprecated = None + self.setter_index = None self._is_trivial_self: bool | None = None if items: # TODO: figure out how to reliably set end position (we don't know the impl here). @@ -598,6 +607,17 @@ def is_trivial_self(self) -> bool: self._is_trivial_self = True return True + @property + def setter(self) -> Decorator: + # Do some consistency checks first. + first_item = self.items[0] + assert isinstance(first_item, Decorator) + assert first_item.var.is_settable_property + assert self.setter_index is not None + item = self.items[self.setter_index] + assert isinstance(item, Decorator) + return item + def accept(self, visitor: StatementVisitor[T]) -> T: return visitor.visit_overloaded_func_def(self) @@ -610,6 +630,7 @@ def serialize(self) -> JsonDict: "impl": None if self.impl is None else self.impl.serialize(), "flags": get_flags(self, FUNCBASE_FLAGS), "deprecated": self.deprecated, + "setter_index": self.setter_index, } @classmethod @@ -630,6 +651,7 @@ def deserialize(cls, data: JsonDict) -> OverloadedFuncDef: res._fullname = data["fullname"] set_flags(res, data["flags"]) res.deprecated = data["deprecated"] + res.setter_index = data["setter_index"] # NOTE: res.info will be set in the fixup phase. return res @@ -3313,8 +3335,8 @@ def enum_members(self) -> list[str]: continue # unannotated value not a member typ = mypy.types.get_proper_type(sym.node.type) - if isinstance( - typ, mypy.types.FunctionLike + if ( + isinstance(typ, mypy.types.FunctionLike) and not typ.is_type_obj() ) or ( # explicit `@member` is required isinstance(typ, mypy.types.Instance) and typ.type.fullname == "enum.nonmember" @@ -3371,11 +3393,11 @@ def calculate_metaclass_type(self) -> mypy.types.Instance | None: return c return None - def is_metaclass(self) -> bool: + def is_metaclass(self, *, precise: bool = False) -> bool: return ( self.has_base("builtins.type") or self.fullname == "abc.ABCMeta" - or self.fallback_to_any + or (self.fallback_to_any and not precise) ) def has_base(self, fullname: str) -> bool: diff --git a/mypy/semanal.py b/mypy/semanal.py index 89bb5ab97c2a..bc64e7e7ca53 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1558,6 +1558,7 @@ def analyze_property_with_multi_part_definition( ) assert isinstance(setter_func_type, CallableType) bare_setter_type = setter_func_type + defn.setter_index = i + 1 if first_node.name == "deleter": item.func.abstract_status = first_item.func.abstract_status for other_node in item.decorators[1:]: diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index 1b0cc218ed16..16a0d882a8aa 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -460,6 +460,7 @@ def visit_callable_type(self, typ: CallableType) -> SnapshotItem: typ.is_type_obj(), typ.is_ellipsis_args, snapshot_types(typ.variables), + typ.is_bound, ) def normalize_callable_variables(self, typ: CallableType) -> CallableType: diff --git a/mypy/typeops.py b/mypy/typeops.py index bcf946900563..1a29054513a2 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -185,6 +185,7 @@ def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> P arg_kinds=[ARG_STAR, ARG_STAR2], arg_names=["_args", "_kwds"], ret_type=any_type, + is_bound=True, fallback=named_type("builtins.function"), ) return class_callable(sig, info, fallback, None, is_new=False) @@ -479,7 +480,7 @@ class B(A): pass arg_kinds=func.arg_kinds[1:], arg_names=func.arg_names[1:], variables=variables, - bound_args=[original_type], + is_bound=True, ) return cast(F, res) @@ -1257,7 +1258,7 @@ def named_type(fullname: str) -> Instance: return type_object_type(left.type, named_type) - if member == "__call__" and left.type.is_metaclass(): + if member == "__call__" and left.type.is_metaclass(precise=True): # Special case: we want to avoid falling back to metaclass __call__ # if constructor signature didn't match, this can cause many false negatives. return None diff --git a/mypy/types.py b/mypy/types.py index 41a958ae93cc..7f67c303a929 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1597,6 +1597,9 @@ def with_name(self, name: str) -> FunctionLike: def get_name(self) -> str | None: pass + def bound(self) -> bool: + return bool(self.items) and self.items[0].is_bound + class FormalArgument(NamedTuple): name: str | None @@ -1826,8 +1829,7 @@ class CallableType(FunctionLike): # 'dict' and 'partial' for a `functools.partial` evaluation) "from_type_type", # Was this callable generated by analyzing Type[...] # instantiation? - "bound_args", # Bound type args, mostly unused but may be useful for - # tools that consume mypy ASTs + "is_bound", # Is this a bound method? "def_extras", # Information about original definition we want to serialize. # This is used for more detailed error messages. "type_guard", # T, if -> TypeGuard[T] (ret_type is bool in this case). @@ -1855,7 +1857,7 @@ def __init__( implicit: bool = False, special_sig: str | None = None, from_type_type: bool = False, - bound_args: Sequence[Type | None] = (), + is_bound: bool = False, def_extras: dict[str, Any] | None = None, type_guard: Type | None = None, type_is: Type | None = None, @@ -1888,9 +1890,7 @@ def __init__( self.from_type_type = from_type_type self.from_concatenate = from_concatenate self.imprecise_arg_kinds = imprecise_arg_kinds - if not bound_args: - bound_args = () - self.bound_args = bound_args + self.is_bound = is_bound if def_extras: self.def_extras = def_extras elif isinstance(definition, FuncDef): @@ -1927,7 +1927,7 @@ def copy_modified( implicit: Bogus[bool] = _dummy, special_sig: Bogus[str | None] = _dummy, from_type_type: Bogus[bool] = _dummy, - bound_args: Bogus[list[Type | None]] = _dummy, + is_bound: Bogus[bool] = _dummy, def_extras: Bogus[dict[str, Any]] = _dummy, type_guard: Bogus[Type | None] = _dummy, type_is: Bogus[Type | None] = _dummy, @@ -1952,7 +1952,7 @@ def copy_modified( implicit=implicit if implicit is not _dummy else self.implicit, special_sig=special_sig if special_sig is not _dummy else self.special_sig, from_type_type=from_type_type if from_type_type is not _dummy else self.from_type_type, - bound_args=bound_args if bound_args is not _dummy else self.bound_args, + is_bound=is_bound if is_bound is not _dummy else self.is_bound, def_extras=def_extras if def_extras is not _dummy else dict(self.def_extras), type_guard=type_guard if type_guard is not _dummy else self.type_guard, type_is=type_is if type_is is not _dummy else self.type_is, @@ -2277,7 +2277,7 @@ def serialize(self) -> JsonDict: "variables": [v.serialize() for v in self.variables], "is_ellipsis_args": self.is_ellipsis_args, "implicit": self.implicit, - "bound_args": [(None if t is None else t.serialize()) for t in self.bound_args], + "is_bound": self.is_bound, "def_extras": dict(self.def_extras), "type_guard": self.type_guard.serialize() if self.type_guard is not None else None, "type_is": (self.type_is.serialize() if self.type_is is not None else None), @@ -2300,7 +2300,7 @@ def deserialize(cls, data: JsonDict) -> CallableType: variables=[cast(TypeVarLikeType, deserialize_type(v)) for v in data["variables"]], is_ellipsis_args=data["is_ellipsis_args"], implicit=data["implicit"], - bound_args=[(None if t is None else deserialize_type(t)) for t in data["bound_args"]], + is_bound=data["is_bound"], def_extras=data["def_extras"], type_guard=( deserialize_type(data["type_guard"]) if data["type_guard"] is not None else None diff --git a/mypy/util.py b/mypy/util.py index d3f49f74bbae..d7ff2a367fa2 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -66,7 +66,7 @@ def is_dunder(name: str, exclude_special: bool = False) -> bool: def is_sunder(name: str) -> bool: - return not is_dunder(name) and name.startswith("_") and name.endswith("_") + return not is_dunder(name) and name.startswith("_") and name.endswith("_") and name != "_" def split_module_names(mod_name: str) -> list[str]: diff --git a/mypy/version.py b/mypy/version.py index ffebfb7aa9ad..a003b2b70558 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.16.0+dev" +__version__ = "1.16.1" base_version = __version__ mypy_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) diff --git a/mypyc/codegen/emitclass.py b/mypyc/codegen/emitclass.py index c5191e5fb939..9cb9074b9fc4 100644 --- a/mypyc/codegen/emitclass.py +++ b/mypyc/codegen/emitclass.py @@ -304,9 +304,6 @@ def emit_line() -> None: emit_line() generate_dealloc_for_class(cl, dealloc_name, clear_name, bool(del_method), emitter) emit_line() - if del_method: - generate_finalize_for_class(del_method, finalize_name, emitter) - emit_line() if cl.allow_interpreted_subclasses: shadow_vtable_name: str | None = generate_vtables( @@ -317,6 +314,9 @@ def emit_line() -> None: shadow_vtable_name = None vtable_name = generate_vtables(cl, vtable_setup_name, vtable_name, emitter, shadow=False) emit_line() + if del_method: + generate_finalize_for_class(del_method, finalize_name, emitter) + emit_line() if needs_getseters: generate_getseter_declarations(cl, emitter) emit_line() diff --git a/mypyc/doc/native_classes.rst b/mypyc/doc/native_classes.rst index 7f892de3e239..dbcf238b78d5 100644 --- a/mypyc/doc/native_classes.rst +++ b/mypyc/doc/native_classes.rst @@ -48,11 +48,13 @@ can be assigned to (similar to using ``__slots__``):: Inheritance ----------- -Only single inheritance is supported (except for :ref:`traits -`). Most non-native classes can't be used as base -classes. +Only single inheritance is supported from native classes (except for +:ref:`traits `). Most non-native extension classes can't +be used as base classes, but regular Python classes can be used as +base classes unless they use unsupported metaclasses (see below for +more about this). -These non-native classes can be used as base classes of native +These non-native extension classes can be used as base classes of native classes: * ``object`` @@ -63,8 +65,6 @@ classes: * ``IndexError`` * ``LookupError`` * ``UserWarning`` -* ``typing.NamedTuple`` -* ``enum.Enum`` By default, a non-native class can't inherit a native class, and you can't inherit from a native class outside the compilation unit that @@ -89,6 +89,15 @@ You need to install ``mypy-extensions`` to use ``@mypyc_attr``: pip install --upgrade mypy-extensions +Additionally, mypyc recognizes these base classes as special, and +understands how they alter the behavior of classes (including native +classes) that subclass them: + +* ``typing.NamedTuple`` +* ``typing.Generic`` +* ``typing.Protocol`` +* ``enum.Enum`` + Class variables --------------- @@ -145,7 +154,8 @@ behavior is too dynamic. You can use these metaclasses, however: .. note:: If a class definition uses an unsupported metaclass, *mypyc - compiles the class into a regular Python class*. + compiles the class into a regular Python class* (non-native + class). Class decorators ---------------- @@ -165,7 +175,63 @@ efficient as pure native classes. .. note:: If a class definition uses an unsupported class decorator, *mypyc - compiles the class into a regular Python class*. + compiles the class into a regular Python class* (non-native class). + +Defining non-native classes +--------------------------- + +You can use the ``@mypy_extensions.mypyc_attr(...)`` class decorator +with an argument ``native_class=False`` to explicitly define normal +Python classes (non-native classes):: + + from mypy_extensions import mypyc_attr + + @mypyc_attr(native_class=False) + class NonNative: + def __init__(self) -> None: + self.attr = 1 + + setattr(NonNative, "extra", 1) # Ok + +This only has an effect in classes compiled using mypyc. Non-native +classes are significantly less efficient than native classes, but they +are sometimes necessary to work around the limitations of native classes. + +Non-native classes can use arbitrary metaclasses and class decorators, +and they support flexible multiple inheritance. Mypyc will still +generate a compile-time error if you try to assign to a method, or an +attribute that is not defined in a class body, since these are static +type errors detected by mypy:: + + o = NonNative() + o.extra = "x" # Static type error: "extra" not defined + +However, these operations still work at runtime, including in modules +that are not compiled using mypyc. You can also use ``setattr`` and +``getattr`` for dynamic access of arbitrary attributes. Expressions +with an ``Any`` type are also not type checked statically, allowing +access to arbitrary attributes:: + + a: Any = o + a.extra = "x" # Ok + + setattr(o, "extra", "y") # Also ok + +Implicit non-native classes +--------------------------- + +If a compiled class uses an unsupported metaclass or an unsupported +class decorator, it will implicitly be a non-native class, as +discussed above. You can still use ``@mypyc_attr(native_class=False)`` +to explicitly mark it as a non-native class. + +Explicit native classes +----------------------- + +You can use ``@mypyc_attr(native_class=True)`` to explicitly declare a +class as a native class. It will be a compile-time error if mypyc +can't compile the class as a native class. You can use this to avoid +accidentally defining implicit non-native classes. Deleting attributes ------------------- diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index 97bc063dd8ea..288f281c0a94 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -2754,6 +2754,21 @@ def test_function(): assert(isinstance(d.fitem, ForwardDefinedClass)) assert(isinstance(d.fitems, ForwardDefinedClass)) +[case testDelForDictSubclass-xfail] +# The crash in issue mypy#19175 is fixed. +# But, for classes that derive from built-in Python classes, user-defined __del__ method is not +# being invoked. +class DictSubclass(dict): + def __del__(self): + print("deleting DictSubclass...") + +[file driver.py] +import native +native.DictSubclass() + +[out] +deleting DictSubclass... + [case testDel] class A: def __del__(self): @@ -2774,6 +2789,12 @@ class C(B): class D(A): pass +# Just make sure that this class compiles (see issue mypy#19175). testDelForDictSubclass tests for +# correct output. +class NormDict(dict): + def __del__(self) -> None: + pass + [file driver.py] import native native.C() diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index e0ea00aee361..10e15139218f 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4292,7 +4292,7 @@ int.__eq__(3, 4) [builtins fixtures/args.pyi] [out] main:33: error: Too few arguments for "__eq__" of "int" -main:33: error: Unsupported operand types for == ("int" and "Type[int]") +main:33: error: Unsupported operand types for == ("Type[int]" and "Type[int]") [case testDupBaseClasses] class A: @@ -8726,3 +8726,36 @@ class Fields: reveal_type(Fields.bool_f) # N: Revealed type is "__main__.BoolField" reveal_type(Fields.int_f) # N: Revealed type is "__main__.NumField" reveal_type(Fields.custom_f) # N: Revealed type is "__main__.AnyField[__main__.Custom]" + +[case testRecursivePropertyWithInvalidSetterNoCrash] +class NoopPowerResource: + _hardware_type: int + + @property + def hardware_type(self) -> int: + return self._hardware_type + + @hardware_type.setter + def hardware_type(self) -> None: # E: Invalid property setter signature + self.hardware_type = None # Note: intentionally recursive +[builtins fixtures/property.pyi] + +[case testPropertyAllowsDeleterBeforeSetter] +class C: + @property + def foo(self) -> str: ... + @foo.deleter + def foo(self) -> None: ... + @foo.setter + def foo(self, val: int) -> None: ... + + @property + def bar(self) -> int: ... + @bar.deleter + def bar(self) -> None: ... + @bar.setter + def bar(self, value: int, val: int) -> None: ... # E: Invalid property setter signature + +C().foo = "no" # E: Incompatible types in assignment (expression has type "str", variable has type "int") +C().bar = "fine" +[builtins fixtures/property.pyi] diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index cc9048db18dc..3001bc9a69cb 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -2524,3 +2524,18 @@ class Base: self.o = Enum("o", names) # E: Enum type as attribute is not supported \ # E: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members [builtins fixtures/tuple.pyi] + +[case testSingleUnderscoreNameEnumMember] +# flags: --warn-unreachable + +# https://github.com/python/mypy/issues/19271 +from enum import Enum + +class Things(Enum): + _ = "under score" + +def check(thing: Things) -> None: + if thing is Things._: + return None + return None # E: Statement is unreachable +[builtins fixtures/enum.pyi] diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index ac93c6c20354..35d76a65ccc3 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -3599,3 +3599,45 @@ class Bar(Foo): def foo(self, value: Union[int, str]) -> Union[int, str]: return super().foo(value) # E: Call to abstract method "foo" of "Foo" with trivial body via super() is unsafe + +[case testBoundMethodsAssignedInClassBody] +from typing import Callable + +class A: + def f(self, x: int) -> str: + pass + @classmethod + def g(cls, x: int) -> str: + pass + @staticmethod + def h(x: int) -> str: + pass + attr: Callable[[int], str] + +class C: + x1 = A.f + x2 = A.g + x3 = A().f + x4 = A().g + x5 = A.h + x6 = A().h + x7 = A().attr + +reveal_type(C.x1) # N: Revealed type is "def (self: __main__.A, x: builtins.int) -> builtins.str" +reveal_type(C.x2) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C.x3) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C.x4) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C.x5) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C.x6) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C.x7) # N: Revealed type is "def (builtins.int) -> builtins.str" + +reveal_type(C().x1) # E: Invalid self argument "C" to attribute function "x1" with type "Callable[[A, int], str]" \ + # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C().x2) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C().x3) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C().x4) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C().x5) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C().x6) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C().x7) # E: Invalid self argument "C" to attribute function "x7" with type "Callable[[int], str]" \ + # N: Revealed type is "def () -> builtins.str" +[builtins fixtures/classmethod.pyi] diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 9d5902246ae5..fabd7e294d9a 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -6862,3 +6862,27 @@ if int(): [out] [out2] main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testMethodMakeBoundIncremental] +from a import A +a = A() +a.f() +[file a.py] +class B: + def f(self, s: A) -> int: ... + +def f(s: A) -> int: ... + +class A: + f = f +[file a.py.2] +class B: + def f(self, s: A) -> int: ... + +def f(s: A) -> int: ... + +class A: + f = B().f +[out] +[out2] +main:3: error: Too few arguments diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 25565946158e..093924ab2e26 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -3979,3 +3979,24 @@ def check(mapping: Mapping[str, _T]) -> None: reveal_type(ok1) # N: Revealed type is "Union[_T`-1, builtins.str]" ok2: Union[_T, str] = mapping.get("", "") [builtins fixtures/tuple.pyi] + +[case testNoCrashOnPartialTypeAsContext] +from typing import overload, TypeVar, Optional, Protocol + +T = TypeVar("T") +class DbManager(Protocol): + @overload + def get(self, key: str) -> Optional[T]: + pass + + @overload + def get(self, key: str, default: T) -> T: + pass + +class Foo: + def __init__(self, db: DbManager, bar: bool) -> None: + if bar: + self.qux = db.get("qux") + else: + self.qux = {} # E: Need type annotation for "qux" (hint: "qux: Dict[, ] = ...") +[builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 34e3f3e88080..56c3d72d23e2 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -4460,3 +4460,25 @@ f2(a4) # E: Argument 1 to "f2" has incompatible type "A4"; expected "P2" \ # N: foo: expected "B1", got "str" \ # N: foo: expected setter type "C1", got "str" [builtins fixtures/property.pyi] + +[case testInferCallableProtoWithAnySubclass] +from typing import Any, Generic, Protocol, TypeVar + +T = TypeVar("T", covariant=True) + +Unknown: Any +class Mock(Unknown): + def __init__(self, **kwargs: Any) -> None: ... + def __call__(self, *args: Any, **kwargs: Any) -> Any: ... + +class Factory(Protocol[T]): + def __call__(self, **kwargs: Any) -> T: ... + + +class Test(Generic[T]): + def __init__(self, f: Factory[T]) -> None: + ... + +t = Test(Mock()) +reveal_type(t) # N: Revealed type is "__main__.Test[Any]" +[builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index c2e2e5bddb34..af3982f6accd 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -2638,3 +2638,24 @@ def f2() -> None: return reveal_type(y) # N: Revealed type is "builtins.str" [builtins fixtures/list.pyi] + +[case testEnumTypeObjectMember] +import enum +from typing import NoReturn + +def assert_never(x: NoReturn) -> None: ... + +class ValueType(enum.Enum): + INT = int + STR = str + +value_type: ValueType = ValueType.INT + +match value_type: + case ValueType.INT: + pass + case ValueType.STR: + pass + case _: + assert_never(value_type) +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-typeguard.test b/test-data/unit/check-typeguard.test index 00bf7d211927..94aa7ec6ffb8 100644 --- a/test-data/unit/check-typeguard.test +++ b/test-data/unit/check-typeguard.test @@ -731,62 +731,6 @@ assert a(x=x) reveal_type(x) # N: Revealed type is "builtins.int" [builtins fixtures/tuple.pyi] -[case testTypeGuardInOverloads] -from typing import Any, overload, Union -from typing_extensions import TypeGuard - -@overload -def func1(x: str) -> TypeGuard[str]: - ... - -@overload -def func1(x: int) -> TypeGuard[int]: - ... - -def func1(x: Any) -> Any: - return True - -def func2(val: Any): - if func1(val): - reveal_type(val) # N: Revealed type is "Union[builtins.str, builtins.int]" - else: - reveal_type(val) # N: Revealed type is "Any" - -def func3(val: Union[int, str]): - if func1(val): - reveal_type(val) # N: Revealed type is "Union[builtins.int, builtins.str]" - else: - reveal_type(val) # N: Revealed type is "Union[builtins.int, builtins.str]" - -def func4(val: int): - if func1(val): - reveal_type(val) # N: Revealed type is "builtins.int" - else: - reveal_type(val) # N: Revealed type is "builtins.int" -[builtins fixtures/tuple.pyi] - -[case testTypeIsInOverloadsSameReturn] -from typing import Any, overload, Union -from typing_extensions import TypeGuard - -@overload -def func1(x: str) -> TypeGuard[str]: - ... - -@overload -def func1(x: int) -> TypeGuard[str]: - ... - -def func1(x: Any) -> Any: - return True - -def func2(val: Union[int, str]): - if func1(val): - reveal_type(val) # N: Revealed type is "builtins.str" - else: - reveal_type(val) # N: Revealed type is "Union[builtins.int, builtins.str]" -[builtins fixtures/tuple.pyi] - [case testTypeGuardRestrictAwaySingleInvariant] from typing import List from typing_extensions import TypeGuard diff --git a/test-data/unit/check-typeis.test b/test-data/unit/check-typeis.test index 8cdcf8634788..356b1abfdf63 100644 --- a/test-data/unit/check-typeis.test +++ b/test-data/unit/check-typeis.test @@ -818,125 +818,6 @@ accept_typeguard(typeguard) [builtins fixtures/tuple.pyi] -[case testTypeIsInOverloads] -from typing import Any, overload, Union -from typing_extensions import TypeIs - -@overload -def func1(x: str) -> TypeIs[str]: - ... - -@overload -def func1(x: int) -> TypeIs[int]: - ... - -def func1(x: Any) -> Any: - return True - -def func2(val: Any): - if func1(val): - reveal_type(val) # N: Revealed type is "Union[builtins.str, builtins.int]" - else: - reveal_type(val) # N: Revealed type is "Any" - -def func3(val: Union[int, str]): - if func1(val): - reveal_type(val) # N: Revealed type is "Union[builtins.int, builtins.str]" - else: - reveal_type(val) - -def func4(val: int): - if func1(val): - reveal_type(val) # N: Revealed type is "builtins.int" - else: - reveal_type(val) -[builtins fixtures/tuple.pyi] - -[case testTypeIsInOverloadsSameReturn] -from typing import Any, overload, Union -from typing_extensions import TypeIs - -@overload -def func1(x: str) -> TypeIs[str]: - ... - -@overload -def func1(x: int) -> TypeIs[str]: # type: ignore - ... - -def func1(x: Any) -> Any: - return True - -def func2(val: Union[int, str]): - if func1(val): - reveal_type(val) # N: Revealed type is "builtins.str" - else: - reveal_type(val) # N: Revealed type is "builtins.int" -[builtins fixtures/tuple.pyi] - -[case testTypeIsInOverloadsUnionizeError] -from typing import Any, overload, Union -from typing_extensions import TypeIs, TypeGuard - -@overload -def func1(x: str) -> TypeIs[str]: - ... - -@overload -def func1(x: int) -> TypeGuard[int]: - ... - -def func1(x: Any) -> Any: - return True - -def func2(val: Union[int, str]): - if func1(val): - reveal_type(val) # N: Revealed type is "Union[builtins.int, builtins.str]" - else: - reveal_type(val) # N: Revealed type is "Union[builtins.int, builtins.str]" -[builtins fixtures/tuple.pyi] - -[case testTypeIsInOverloadsUnionizeError2] -from typing import Any, overload, Union -from typing_extensions import TypeIs, TypeGuard - -@overload -def func1(x: int) -> TypeGuard[int]: - ... - -@overload -def func1(x: str) -> TypeIs[str]: - ... - -def func1(x: Any) -> Any: - return True - -def func2(val: Union[int, str]): - if func1(val): - reveal_type(val) # N: Revealed type is "Union[builtins.int, builtins.str]" - else: - reveal_type(val) # N: Revealed type is "Union[builtins.int, builtins.str]" -[builtins fixtures/tuple.pyi] - -[case testTypeIsLikeIsDataclass] -from typing import Any, overload, Union, Type -from typing_extensions import TypeIs - -class DataclassInstance: ... - -@overload -def is_dataclass(obj: type) -> TypeIs[Type[DataclassInstance]]: ... -@overload -def is_dataclass(obj: object) -> TypeIs[Union[DataclassInstance, Type[DataclassInstance]]]: ... - -def is_dataclass(obj: Union[type, object]) -> bool: - return False - -def func(arg: Any) -> None: - if is_dataclass(arg): - reveal_type(arg) # N: Revealed type is "Union[Type[__main__.DataclassInstance], __main__.DataclassInstance]" -[builtins fixtures/tuple.pyi] - [case testTypeIsEnumOverlappingUnionExcludesIrrelevant] from enum import Enum from typing import Literal diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index df244b3135e9..eb07f37a8671 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -11217,3 +11217,27 @@ class A: [out] == main:3: error: Property "f" defined in "A" is read-only + +[case testMethodMakeBoundFineGrained] +from a import A +a = A() +a.f() +[file a.py] +class B: + def f(self, s: A) -> int: ... + +def f(s: A) -> int: ... + +class A: + f = f +[file a.py.2] +class B: + def f(self, s: A) -> int: ... + +def f(s: A) -> int: ... + +class A: + f = B().f +[out] +== +main:3: error: Too few arguments