Skip to content

Commit 06e545c

Browse files
committed
Merge branch 'master' into feature/deprecated/descriptors
2 parents 59f601c + 6050204 commit 06e545c

File tree

357 files changed

+8281
-1977
lines changed

Some content is hidden

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

357 files changed

+8281
-1977
lines changed

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
exclude: '^(mypyc/external/)|(mypy/typeshed/)|misc/typeshed_patches' # Exclude all vendored code from lints
22
repos:
33
- repo: https://github.com/pre-commit/pre-commit-hooks
4-
rev: v4.5.0 # must match test-requirements.txt
4+
rev: v4.5.0
55
hooks:
66
- id: trailing-whitespace
77
- id: end-of-file-fixer
88
- repo: https://github.com/psf/black-pre-commit-mirror
9-
rev: 24.8.0 # must match test-requirements.txt
9+
rev: 24.8.0
1010
hooks:
1111
- id: black
1212
exclude: '^(test-data/)'
1313
- repo: https://github.com/astral-sh/ruff-pre-commit
14-
rev: v0.6.9 # must match test-requirements.txt
14+
rev: v0.6.9
1515
hooks:
1616
- id: ruff
1717
args: [--exit-non-zero-on-fix]

CONTRIBUTING.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,16 @@ However, if you wish to do so, you can run the full test suite
6565
like this:
6666

6767
```bash
68-
python3 runtests.py
68+
python runtests.py
6969
```
7070

7171
Some useful commands for running specific tests include:
7272

7373
```bash
7474
# Use mypy to check mypy's own code
75-
python3 runtests.py self
75+
python runtests.py self
7676
# or equivalently:
77-
python3 -m mypy --config-file mypy_self_check.ini -p mypy
77+
python -m mypy --config-file mypy_self_check.ini -p mypy
7878

7979
# Run a single test from the test suite
8080
pytest -n0 -k 'test_name'
@@ -117,7 +117,7 @@ tox -e dev --override testenv:dev.allowlist_externals+=env -- env # inspect the
117117
```
118118

119119
If you don't already have `tox` installed, you can use a virtual environment as
120-
described above to install `tox` via `pip` (e.g., ``python3 -m pip install tox``).
120+
described above to install `tox` via `pip` (e.g., ``python -m pip install tox``).
121121

122122
## First time contributors
123123

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ recursive-include mypy/typeshed *.pyi
99

1010
# mypy and mypyc
1111
include mypy/py.typed
12+
include mypyc/py.typed
1213
recursive-include mypy *.py
1314
recursive-include mypyc *.py
1415

docs/source/command_line.rst

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ imports.
166166
167167
For more details, see :ref:`ignore-missing-imports`.
168168

169+
.. option:: --follow-untyped-imports
170+
171+
This flag makes mypy analyze imports without stubs or a py.typed marker.
172+
169173
.. option:: --follow-imports {normal,silent,skip,error}
170174

171175
This flag adjusts how mypy follows imported modules that were not
@@ -537,12 +541,12 @@ potentially problematic or redundant in some way.
537541

538542
This limitation will be removed in future releases of mypy.
539543

540-
.. option:: --report-deprecated-as-error
544+
.. option:: --report-deprecated-as-note
541545

542-
By default, mypy emits notes if your code imports or uses deprecated
543-
features. This flag converts such notes to errors, causing mypy to
544-
eventually finish with a non-zero exit code. Features are considered
545-
deprecated when decorated with ``warnings.deprecated``.
546+
If error code ``deprecated`` is enabled, mypy emits errors if your code
547+
imports or uses deprecated features. This flag converts such errors to
548+
notes, causing mypy to eventually finish with a zero exit code. Features
549+
are considered deprecated when decorated with ``warnings.deprecated``.
546550

547551
.. _miscellaneous-strictness-flags:
548552

docs/source/config_file.rst

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,18 @@ section of the command line docs.
315315
match the name of the *imported* module, not the module containing the
316316
import statement.
317317

318+
.. confval:: follow_untyped_imports
319+
320+
:type: boolean
321+
:default: False
322+
323+
Typechecks imports from modules that do not have stubs or a py.typed marker.
324+
325+
If this option is used in a per-module section, the module name should
326+
match the name of the *imported* module, not the module containing the
327+
import statement. Note that scanning all unannotated modules might
328+
significantly increase the runtime of your mypy calls.
329+
318330
.. confval:: follow_imports
319331

320332
:type: string
@@ -717,6 +729,14 @@ section of the command line docs.
717729

718730
Note: This option will override disabled error codes from the disable_error_code option.
719731

732+
.. confval:: extra_checks
733+
734+
:type: boolean
735+
:default: False
736+
737+
This flag enables additional checks that are technically correct but may be impractical in real code.
738+
See :option:`mypy --extra-checks` for more info.
739+
720740
.. confval:: implicit_reexport
721741

722742
:type: boolean
@@ -739,23 +759,23 @@ section of the command line docs.
739759
740760
.. confval:: strict_concatenate
741761

742-
:type: boolean
743-
:default: False
762+
:type: boolean
763+
:default: False
744764

745-
Make arguments prepended via ``Concatenate`` be truly positional-only.
765+
Make arguments prepended via ``Concatenate`` be truly positional-only.
746766

747767
.. confval:: strict_equality
748768

749-
:type: boolean
750-
:default: False
769+
:type: boolean
770+
:default: False
751771

752772
Prohibit equality checks, identity checks, and container checks between
753773
non-overlapping types.
754774

755775
.. confval:: strict
756776

757-
:type: boolean
758-
:default: False
777+
:type: boolean
778+
:default: False
759779

760780
Enable all optional error checking flags. You can see the list of
761781
flags enabled by strict mode in the full :option:`mypy --help`

docs/source/error_code_list2.rst

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,13 +236,13 @@ incorrect control flow or conditional checks that are accidentally always true o
236236
Check that imported or used feature is deprecated [deprecated]
237237
--------------------------------------------------------------
238238

239-
By default, mypy generates a note if your code imports a deprecated feature explicitly with a
239+
If you use :option:`--enable-error-code deprecated <mypy --enable-error-code>`,
240+
mypy generates an error if your code imports a deprecated feature explicitly with a
240241
``from mod import depr`` statement or uses a deprecated feature imported otherwise or defined
241242
locally. Features are considered deprecated when decorated with ``warnings.deprecated``, as
242-
specified in `PEP 702 <https://peps.python.org/pep-0702>`_. You can silence single notes via
243-
``# type: ignore[deprecated]`` or turn off this check completely via ``--disable-error-code=deprecated``.
244-
Use the :option:`--report-deprecated-as-error <mypy --report-deprecated-as-error>` option for
245-
more strictness, which turns all such notes into errors.
243+
specified in `PEP 702 <https://peps.python.org/pep-0702>`_.
244+
Use the :option:`--report-deprecated-as-note <mypy --report-deprecated-as-note>` option to
245+
turn all such errors into notes.
246246

247247
.. note::
248248

docs/source/extending_mypy.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,4 @@ Mypy ships ``mypy.plugins.proper_plugin`` plugin which can be useful
245245
for plugin authors, since it finds missing ``get_proper_type()`` calls,
246246
which is a pretty common mistake.
247247

248-
It is recommended to enable it is a part of your plugin's CI.
248+
It is recommended to enable it as a part of your plugin's CI.

docs/source/generics.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ example (Python 3.12 syntax):
146146
from typing import Mapping, Iterator
147147
148148
# This is a generic subclass of Mapping
149-
class MyMapp[KT, VT](Mapping[KT, VT]):
149+
class MyMap[KT, VT](Mapping[KT, VT]):
150150
def __getitem__(self, k: KT) -> VT: ...
151151
def __iter__(self) -> Iterator[KT]: ...
152152
def __len__(self) -> int: ...
@@ -641,7 +641,7 @@ infer the most flexible variance for each class type variable. Here
641641

642642
.. code-block:: python
643643
644-
class Box[T]: # this type is implilicitly covariant
644+
class Box[T]: # this type is implicitly covariant
645645
def __init__(self, content: T) -> None:
646646
self._content = content
647647
@@ -663,12 +663,12 @@ the attribute as ``Final``, the class could still be made covariant:
663663
664664
from typing import Final
665665
666-
class Box[T]: # this type is implilicitly covariant
666+
class Box[T]: # this type is implicitly covariant
667667
def __init__(self, content: T) -> None:
668668
self.content: Final = content
669669
670670
def get_content(self) -> T:
671-
return self._content
671+
return self.content
672672
673673
When using the legacy syntax, mypy assumes that all user-defined generics
674674
are invariant by default. To declare a given generic class as covariant or

docs/source/running_mypy.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,12 @@ not catch errors in its use.
321321
recommend avoiding ``--ignore-missing-imports`` if possible: it's equivalent
322322
to adding a ``# type: ignore`` to all unresolved imports in your codebase.
323323

324+
4. To make mypy typecheck imports from modules without stubs or a py.typed
325+
marker, you can set the :option:`--follow-untyped-imports <mypy --follow-untyped-imports>`
326+
command line flag or set the :confval:`follow_untyped_imports` config file option to True,
327+
either in the global section of your mypy config file, or individually on a
328+
per-module basis.
329+
324330

325331
Library stubs not installed
326332
---------------------------

mypy/binder.py

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from collections import defaultdict
44
from contextlib import contextmanager
5-
from typing import DefaultDict, Iterator, List, Optional, Tuple, Union, cast
5+
from typing import DefaultDict, Iterator, List, NamedTuple, Optional, Tuple, Union
66
from typing_extensions import TypeAlias as _TypeAlias
77

88
from mypy.erasetype import remove_instance_last_known_values
@@ -30,6 +30,11 @@
3030
BindableExpression: _TypeAlias = Union[IndexExpr, MemberExpr, NameExpr]
3131

3232

33+
class CurrentType(NamedTuple):
34+
type: Type
35+
from_assignment: bool
36+
37+
3338
class Frame:
3439
"""A Frame represents a specific point in the execution of a program.
3540
It carries information about the current types of expressions at
@@ -44,7 +49,7 @@ class Frame:
4449

4550
def __init__(self, id: int, conditional_frame: bool = False) -> None:
4651
self.id = id
47-
self.types: dict[Key, Type] = {}
52+
self.types: dict[Key, CurrentType] = {}
4853
self.unreachable = False
4954
self.conditional_frame = conditional_frame
5055
self.suppress_unreachable_warnings = False
@@ -132,18 +137,18 @@ def push_frame(self, conditional_frame: bool = False) -> Frame:
132137
self.options_on_return.append([])
133138
return f
134139

135-
def _put(self, key: Key, type: Type, index: int = -1) -> None:
136-
self.frames[index].types[key] = type
140+
def _put(self, key: Key, type: Type, from_assignment: bool, index: int = -1) -> None:
141+
self.frames[index].types[key] = CurrentType(type, from_assignment)
137142

138-
def _get(self, key: Key, index: int = -1) -> Type | None:
143+
def _get(self, key: Key, index: int = -1) -> CurrentType | None:
139144
if index < 0:
140145
index += len(self.frames)
141146
for i in range(index, -1, -1):
142147
if key in self.frames[i].types:
143148
return self.frames[i].types[key]
144149
return None
145150

146-
def put(self, expr: Expression, typ: Type) -> None:
151+
def put(self, expr: Expression, typ: Type, *, from_assignment: bool = True) -> None:
147152
if not isinstance(expr, (IndexExpr, MemberExpr, NameExpr)):
148153
return
149154
if not literal(expr):
@@ -153,7 +158,7 @@ def put(self, expr: Expression, typ: Type) -> None:
153158
if key not in self.declarations:
154159
self.declarations[key] = get_declaration(expr)
155160
self._add_dependencies(key)
156-
self._put(key, typ)
161+
self._put(key, typ, from_assignment)
157162

158163
def unreachable(self) -> None:
159164
self.frames[-1].unreachable = True
@@ -164,7 +169,10 @@ def suppress_unreachable_warnings(self) -> None:
164169
def get(self, expr: Expression) -> Type | None:
165170
key = literal_hash(expr)
166171
assert key is not None, "Internal error: binder tried to get non-literal"
167-
return self._get(key)
172+
found = self._get(key)
173+
if found is None:
174+
return None
175+
return found.type
168176

169177
def is_unreachable(self) -> bool:
170178
# TODO: Copy the value of unreachable into new frames to avoid
@@ -193,7 +201,7 @@ def update_from_options(self, frames: list[Frame]) -> bool:
193201
If a key is declared as AnyType, only update it if all the
194202
options are the same.
195203
"""
196-
204+
all_reachable = all(not f.unreachable for f in frames)
197205
frames = [f for f in frames if not f.unreachable]
198206
changed = False
199207
keys = {key for f in frames for key in f.types}
@@ -207,17 +215,30 @@ def update_from_options(self, frames: list[Frame]) -> bool:
207215
# know anything about key in at least one possible frame.
208216
continue
209217

210-
type = resulting_values[0]
211-
assert type is not None
218+
if all_reachable and all(
219+
x is not None and not x.from_assignment for x in resulting_values
220+
):
221+
# Do not synthesize a new type if we encountered a conditional block
222+
# (if, while or match-case) without assignments.
223+
# See check-isinstance.test::testNoneCheckDoesNotMakeTypeVarOptional
224+
# This is a safe assumption: the fact that we checked something with `is`
225+
# or `isinstance` does not change the type of the value.
226+
continue
227+
228+
current_type = resulting_values[0]
229+
assert current_type is not None
230+
type = current_type.type
212231
declaration_type = get_proper_type(self.declarations.get(key))
213232
if isinstance(declaration_type, AnyType):
214233
# At this point resulting values can't contain None, see continue above
215-
if not all(is_same_type(type, cast(Type, t)) for t in resulting_values[1:]):
234+
if not all(
235+
t is not None and is_same_type(type, t.type) for t in resulting_values[1:]
236+
):
216237
type = AnyType(TypeOfAny.from_another_any, source_any=declaration_type)
217238
else:
218239
for other in resulting_values[1:]:
219240
assert other is not None
220-
type = join_simple(self.declarations[key], type, other)
241+
type = join_simple(self.declarations[key], type, other.type)
221242
# Try simplifying resulting type for unions involving variadic tuples.
222243
# Technically, everything is still valid without this step, but if we do
223244
# not do this, this may create long unions after exiting an if check like:
@@ -236,8 +257,8 @@ def update_from_options(self, frames: list[Frame]) -> bool:
236257
)
237258
if simplified == self.declarations[key]:
238259
type = simplified
239-
if current_value is None or not is_same_type(type, current_value):
240-
self._put(key, type)
260+
if current_value is None or not is_same_type(type, current_value[0]):
261+
self._put(key, type, from_assignment=True)
241262
changed = True
242263

243264
self.frames[-1].unreachable = not frames
@@ -374,7 +395,9 @@ def most_recent_enclosing_type(self, expr: BindableExpression, type: Type) -> Ty
374395
key = literal_hash(expr)
375396
assert key is not None
376397
enclosers = [get_declaration(expr)] + [
377-
f.types[key] for f in self.frames if key in f.types and is_subtype(type, f.types[key])
398+
f.types[key].type
399+
for f in self.frames
400+
if key in f.types and is_subtype(type, f.types[key][0])
378401
]
379402
return enclosers[-1]
380403

0 commit comments

Comments
 (0)