Skip to content

Commit 51ccf23

Browse files
authored
Merge branch 'master' into plugins-from-cli
2 parents 74da5c6 + d39eacc commit 51ccf23

Some content is hidden

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

87 files changed

+1348
-618
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]

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

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: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2367,10 +2367,11 @@ def erase_override(t: Type) -> Type:
23672367
else:
23682368
continue
23692369
if not is_subtype(original_arg_type, erase_override(override_arg_type)):
2370+
context: Context = node
23702371
if isinstance(node, FuncDef) and not node.is_property:
2371-
context: Context = node.arguments[i + len(override.bound_args)]
2372-
else:
2373-
context = node
2372+
arg_node = node.arguments[i + len(override.bound_args)]
2373+
if arg_node.line != -1:
2374+
context = arg_node
23742375
self.msg.argument_incompatible_with_supertype(
23752376
i + 1,
23762377
name,
@@ -4724,11 +4725,11 @@ def visit_if_stmt(self, s: IfStmt) -> None:
47244725

47254726
# XXX Issue a warning if condition is always False?
47264727
with self.binder.frame_context(can_skip=True, fall_through=2):
4727-
self.push_type_map(if_map)
4728+
self.push_type_map(if_map, from_assignment=False)
47284729
self.accept(b)
47294730

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

47334734
with self.binder.frame_context(can_skip=False, fall_through=2):
47344735
if s.else_body:
@@ -5309,18 +5310,21 @@ def visit_match_stmt(self, s: MatchStmt) -> None:
53095310
if b.is_unreachable or isinstance(
53105311
get_proper_type(pattern_type.type), UninhabitedType
53115312
):
5312-
self.push_type_map(None)
5313+
self.push_type_map(None, from_assignment=False)
53135314
else_map: TypeMap = {}
53145315
else:
53155316
pattern_map, else_map = conditional_types_to_typemaps(
53165317
named_subject, pattern_type.type, pattern_type.rest_type
53175318
)
53185319
self.remove_capture_conflicts(pattern_type.captures, inferred_types)
5319-
self.push_type_map(pattern_map)
5320+
self.push_type_map(pattern_map, from_assignment=False)
53205321
if pattern_map:
53215322
for expr, typ in pattern_map.items():
5322-
self.push_type_map(self._get_recursive_sub_patterns_map(expr, typ))
5323-
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)
53245328
if g is not None:
53255329
with self.binder.frame_context(can_skip=False, fall_through=3):
53265330
gt = get_proper_type(self.expr_checker.accept(g))
@@ -5346,11 +5350,11 @@ def visit_match_stmt(self, s: MatchStmt) -> None:
53465350
continue
53475351
type_map[named_subject] = type_map[expr]
53485352

5349-
self.push_type_map(guard_map)
5353+
self.push_type_map(guard_map, from_assignment=False)
53505354
self.accept(b)
53515355
else:
53525356
self.accept(b)
5353-
self.push_type_map(else_map)
5357+
self.push_type_map(else_map, from_assignment=False)
53545358

53555359
# This is needed due to a quirk in frame_context. Without it types will stay narrowed
53565360
# after the match.
@@ -6273,10 +6277,6 @@ def has_no_custom_eq_checks(t: Type) -> bool:
62736277
coerce_only_in_literal_context,
62746278
)
62756279

6276-
# Strictly speaking, we should also skip this check if the objects in the expr
6277-
# chain have custom __eq__ or __ne__ methods. But we (maybe optimistically)
6278-
# assume nobody would actually create a custom objects that considers itself
6279-
# equal to None.
62806280
if if_map == {} and else_map == {}:
62816281
if_map, else_map = self.refine_away_none_in_comparison(
62826282
operands, operand_types, expr_indices, narrowable_operand_index_to_hash.keys()
@@ -6601,25 +6601,36 @@ def refine_away_none_in_comparison(
66016601
For more details about what the different arguments mean, see the
66026602
docstring of 'refine_identity_comparison_expression' up above.
66036603
"""
6604+
66046605
non_optional_types = []
66056606
for i in chain_indices:
66066607
typ = operand_types[i]
66076608
if not is_overlapping_none(typ):
66086609
non_optional_types.append(typ)
66096610

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

6614-
if_map = {}
6615-
for i in narrowable_operand_indices:
6616-
expr_type = operand_types[i]
6617-
if not is_overlapping_none(expr_type):
6618-
continue
6619-
if any(is_overlapping_erased_types(expr_type, t) for t in non_optional_types):
6620-
if_map[operands[i]] = remove_optional(expr_type)
6613+
if not non_optional_types or (len(non_optional_types) != len(chain_indices)):
6614+
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)
66216632

6622-
return if_map, {}
6633+
return if_map, else_map
66236634

66246635
def is_len_of_tuple(self, expr: Expression) -> bool:
66256636
"""Is this expression a `len(x)` call where x is a tuple or union of tuples?"""
@@ -7364,12 +7375,12 @@ def iterable_item_type(
73647375
def function_type(self, func: FuncBase) -> FunctionLike:
73657376
return function_type(func, self.named_type("builtins.function"))
73667377

7367-
def push_type_map(self, type_map: TypeMap) -> None:
7378+
def push_type_map(self, type_map: TypeMap, *, from_assignment: bool = True) -> None:
73687379
if type_map is None:
73697380
self.binder.unreachable()
73707381
else:
73717382
for expr, type in type_map.items():
7372-
self.binder.put(expr, type)
7383+
self.binder.put(expr, type, from_assignment=from_assignment)
73737384

73747385
def infer_issubclass_maps(self, node: CallExpr, expr: Expression) -> tuple[TypeMap, TypeMap]:
73757386
"""Infer type restrictions for an expression in issubclass call."""
@@ -7742,9 +7753,7 @@ def conditional_types(
77427753
) and is_proper_subtype(current_type, proposed_type, ignore_promotions=True):
77437754
# Expression is always of one of the types in proposed_type_ranges
77447755
return default, UninhabitedType()
7745-
elif not is_overlapping_types(
7746-
current_type, proposed_type, prohibit_none_typevar_overlap=True, ignore_promotions=True
7747-
):
7756+
elif not is_overlapping_types(current_type, proposed_type, ignore_promotions=True):
77487757
# Expression is never of any type in proposed_type_ranges
77497758
return UninhabitedType(), default
77507759
else:

mypy/checkexpr.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5612,11 +5612,15 @@ def visit_slice_expr(self, e: SliceExpr) -> Type:
56125612
except KeyError:
56135613
supports_index = self.chk.named_type("builtins.int") # thanks, fixture life
56145614
expected = make_optional_type(supports_index)
5615+
type_args = []
56155616
for index in [e.begin_index, e.end_index, e.stride]:
56165617
if index:
56175618
t = self.accept(index)
56185619
self.chk.check_subtype(t, expected, index, message_registry.INVALID_SLICE_INDEX)
5619-
return self.named_type("builtins.slice")
5620+
type_args.append(t)
5621+
else:
5622+
type_args.append(NoneType())
5623+
return self.chk.named_generic_type("builtins.slice", type_args)
56205624

56215625
def visit_list_comprehension(self, e: ListComprehension) -> Type:
56225626
return self.check_generator_or_comprehension(

0 commit comments

Comments
 (0)