Skip to content

Commit c1adb5c

Browse files
authored
Merge branch 'python:master' into master
2 parents 8f982d5 + 71ec4a6 commit c1adb5c

File tree

301 files changed

+7203
-1469
lines changed

Some content is hidden

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

301 files changed

+7203
-1469
lines changed

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: 12 additions & 0 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

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/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

mypy/checker.py

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4725,11 +4725,11 @@ def visit_if_stmt(self, s: IfStmt) -> None:
47254725

47264726
# XXX Issue a warning if condition is always False?
47274727
with self.binder.frame_context(can_skip=True, fall_through=2):
4728-
self.push_type_map(if_map)
4728+
self.push_type_map(if_map, from_assignment=False)
47294729
self.accept(b)
47304730

47314731
# XXX Issue a warning if condition is always True?
4732-
self.push_type_map(else_map)
4732+
self.push_type_map(else_map, from_assignment=False)
47334733

47344734
with self.binder.frame_context(can_skip=False, fall_through=2):
47354735
if s.else_body:
@@ -5310,18 +5310,21 @@ def visit_match_stmt(self, s: MatchStmt) -> None:
53105310
if b.is_unreachable or isinstance(
53115311
get_proper_type(pattern_type.type), UninhabitedType
53125312
):
5313-
self.push_type_map(None)
5313+
self.push_type_map(None, from_assignment=False)
53145314
else_map: TypeMap = {}
53155315
else:
53165316
pattern_map, else_map = conditional_types_to_typemaps(
53175317
named_subject, pattern_type.type, pattern_type.rest_type
53185318
)
53195319
self.remove_capture_conflicts(pattern_type.captures, inferred_types)
5320-
self.push_type_map(pattern_map)
5320+
self.push_type_map(pattern_map, from_assignment=False)
53215321
if pattern_map:
53225322
for expr, typ in pattern_map.items():
5323-
self.push_type_map(self._get_recursive_sub_patterns_map(expr, typ))
5324-
self.push_type_map(pattern_type.captures)
5323+
self.push_type_map(
5324+
self._get_recursive_sub_patterns_map(expr, typ),
5325+
from_assignment=False,
5326+
)
5327+
self.push_type_map(pattern_type.captures, from_assignment=False)
53255328
if g is not None:
53265329
with self.binder.frame_context(can_skip=False, fall_through=3):
53275330
gt = get_proper_type(self.expr_checker.accept(g))
@@ -5347,11 +5350,11 @@ def visit_match_stmt(self, s: MatchStmt) -> None:
53475350
continue
53485351
type_map[named_subject] = type_map[expr]
53495352

5350-
self.push_type_map(guard_map)
5353+
self.push_type_map(guard_map, from_assignment=False)
53515354
self.accept(b)
53525355
else:
53535356
self.accept(b)
5354-
self.push_type_map(else_map)
5357+
self.push_type_map(else_map, from_assignment=False)
53555358

53565359
# This is needed due to a quirk in frame_context. Without it types will stay narrowed
53575360
# after the match.
@@ -6274,10 +6277,6 @@ def has_no_custom_eq_checks(t: Type) -> bool:
62746277
coerce_only_in_literal_context,
62756278
)
62766279

6277-
# Strictly speaking, we should also skip this check if the objects in the expr
6278-
# chain have custom __eq__ or __ne__ methods. But we (maybe optimistically)
6279-
# assume nobody would actually create a custom objects that considers itself
6280-
# equal to None.
62816280
if if_map == {} and else_map == {}:
62826281
if_map, else_map = self.refine_away_none_in_comparison(
62836282
operands, operand_types, expr_indices, narrowable_operand_index_to_hash.keys()
@@ -6602,25 +6601,36 @@ def refine_away_none_in_comparison(
66026601
For more details about what the different arguments mean, see the
66036602
docstring of 'refine_identity_comparison_expression' up above.
66046603
"""
6604+
66056605
non_optional_types = []
66066606
for i in chain_indices:
66076607
typ = operand_types[i]
66086608
if not is_overlapping_none(typ):
66096609
non_optional_types.append(typ)
66106610

6611-
# Make sure we have a mixture of optional and non-optional types.
6612-
if len(non_optional_types) == 0 or len(non_optional_types) == len(chain_indices):
6613-
return {}, {}
6611+
if_map, else_map = {}, {}
66146612

6615-
if_map = {}
6616-
for i in narrowable_operand_indices:
6617-
expr_type = operand_types[i]
6618-
if not is_overlapping_none(expr_type):
6619-
continue
6620-
if any(is_overlapping_erased_types(expr_type, t) for t in non_optional_types):
6621-
if_map[operands[i]] = remove_optional(expr_type)
6613+
if not non_optional_types or (len(non_optional_types) != len(chain_indices)):
66226614

6623-
return if_map, {}
6615+
# Narrow e.g. `Optional[A] == "x"` or `Optional[A] is "x"` to `A` (which may be
6616+
# convenient but is strictly not type-safe):
6617+
for i in narrowable_operand_indices:
6618+
expr_type = operand_types[i]
6619+
if not is_overlapping_none(expr_type):
6620+
continue
6621+
if any(is_overlapping_erased_types(expr_type, t) for t in non_optional_types):
6622+
if_map[operands[i]] = remove_optional(expr_type)
6623+
6624+
# Narrow e.g. `Optional[A] != None` to `A` (which is stricter than the above step and
6625+
# so type-safe but less convenient, because e.g. `Optional[A] == None` still results
6626+
# in `Optional[A]`):
6627+
if any(isinstance(get_proper_type(ot), NoneType) for ot in operand_types):
6628+
for i in narrowable_operand_indices:
6629+
expr_type = operand_types[i]
6630+
if is_overlapping_none(expr_type):
6631+
else_map[operands[i]] = remove_optional(expr_type)
6632+
6633+
return if_map, else_map
66246634

66256635
def is_len_of_tuple(self, expr: Expression) -> bool:
66266636
"""Is this expression a `len(x)` call where x is a tuple or union of tuples?"""
@@ -7365,12 +7375,12 @@ def iterable_item_type(
73657375
def function_type(self, func: FuncBase) -> FunctionLike:
73667376
return function_type(func, self.named_type("builtins.function"))
73677377

7368-
def push_type_map(self, type_map: TypeMap) -> None:
7378+
def push_type_map(self, type_map: TypeMap, *, from_assignment: bool = True) -> None:
73697379
if type_map is None:
73707380
self.binder.unreachable()
73717381
else:
73727382
for expr, type in type_map.items():
7373-
self.binder.put(expr, type)
7383+
self.binder.put(expr, type, from_assignment=from_assignment)
73747384

73757385
def infer_issubclass_maps(self, node: CallExpr, expr: Expression) -> tuple[TypeMap, TypeMap]:
73767386
"""Infer type restrictions for an expression in issubclass call."""
@@ -7686,7 +7696,7 @@ def warn_deprecated(self, node: SymbolNode | None, context: Context) -> None:
76867696
and ((deprecated := node.deprecated) is not None)
76877697
and not self.is_typeshed_stub
76887698
):
7689-
warn = self.msg.fail if self.options.report_deprecated_as_error else self.msg.note
7699+
warn = self.msg.note if self.options.report_deprecated_as_note else self.msg.fail
76907700
warn(deprecated, context, code=codes.DEPRECATED)
76917701

76927702

@@ -7743,9 +7753,7 @@ def conditional_types(
77437753
) and is_proper_subtype(current_type, proposed_type, ignore_promotions=True):
77447754
# Expression is always of one of the types in proposed_type_ranges
77457755
return default, UninhabitedType()
7746-
elif not is_overlapping_types(
7747-
current_type, proposed_type, prohibit_none_typevar_overlap=True, ignore_promotions=True
7748-
):
7756+
elif not is_overlapping_types(current_type, proposed_type, ignore_promotions=True):
77497757
# Expression is never of any type in proposed_type_ranges
77507758
return UninhabitedType(), default
77517759
else:

0 commit comments

Comments
 (0)