Skip to content

Commit d5d1c23

Browse files
committed
Merge remote-tracking branch 'upstream/master' into feature/st-normalize-tuple-fallback
2 parents 75b09ac + 653fc9b commit d5d1c23

Some content is hidden

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

72 files changed

+1026
-489
lines changed

CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ Mypy is up to 40% faster in some use cases. This improvement comes largely from
2020
of the garbage collector. Additionally, the release includes several micro-optimizations that may
2121
be impactful for large projects.
2222

23-
Contributed by Jukka Lehtosalo (PR [18306](https://github.com/python/mypy/pull/18306),
24-
PR [18302](https://github.com/python/mypy/pull/18302, PR [18298](https://github.com/python/mypy/pull/18298,
25-
PR [18299](https://github.com/python/mypy/pull/18299).
23+
Contributed by Jukka Lehtosalo
24+
- PR [18306](https://github.com/python/mypy/pull/18306)
25+
- PR [18302](https://github.com/python/mypy/pull/18302)
26+
- PR [18298](https://github.com/python/mypy/pull/18298)
27+
- PR [18299](https://github.com/python/mypy/pull/18299)
2628

2729
### Mypyc Accelerated Mypy Wheels for ARM Linux
2830

docs/source/generics.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -999,7 +999,7 @@ similarly supported via generics (Python 3.12 syntax):
999999

10001000
.. code-block:: python
10011001
1002-
from colletions.abc import Callable
1002+
from collections.abc import Callable
10031003
from typing import Any
10041004
10051005
def route[F: Callable[..., Any]](url: str) -> Callable[[F], F]:

docs/source/more_types.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ program:
390390
The ``summarize([])`` call matches both variants: an empty list could
391391
be either a ``list[int]`` or a ``list[str]``. In this case, mypy
392392
will break the tie by picking the first matching variant: ``output``
393-
will have an inferred type of ``float``. The implementor is responsible
393+
will have an inferred type of ``float``. The implementer is responsible
394394
for making sure ``summarize`` breaks ties in the same way at runtime.
395395

396396
However, there are two exceptions to the "pick the first match" rule.

docs/source/runtime_troubles.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ libraries if types are generic only in stubs.
274274
Using types defined in stubs but not at runtime
275275
-----------------------------------------------
276276

277-
Sometimes stubs that you're using may define types you wish to re-use that do
277+
Sometimes stubs that you're using may define types you wish to reuse that do
278278
not exist at runtime. Importing these types naively will cause your code to fail
279279
at runtime with ``ImportError`` or ``ModuleNotFoundError``. Similar to previous
280280
sections, these can be dealt with by using :ref:`typing.TYPE_CHECKING

misc/gen_blog_post_html.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def convert(src: str) -> str:
9595
h = re.sub(r"`\*\*`", "<tt>**</tt>", h)
9696

9797
# Paragraphs
98-
h = re.sub(r"\n([A-Z])", r"\n<p>\1", h)
98+
h = re.sub(r"\n\n([A-Z])", r"\n\n<p>\1", h)
9999

100100
# Bullet lists
101101
h = format_lists(h)
@@ -104,6 +104,7 @@ def convert(src: str) -> str:
104104
h = format_code(h)
105105

106106
# Code fragments
107+
h = re.sub(r"``([^`]+)``", r"<tt>\1</tt>", h)
107108
h = re.sub(r"`([^`]+)`", r"<tt>\1</tt>", h)
108109

109110
# Remove **** noise
@@ -125,7 +126,9 @@ def convert(src: str) -> str:
125126
r'fixes issue <a href="https://github.com/python/mypy/issues/\1">\1</a>',
126127
h,
127128
)
128-
h = re.sub(r"#([0-9]+)", r'PR <a href="https://github.com/python/mypy/pull/\1">\1</a>', h)
129+
# Note the leading space to avoid stomping on strings that contain #\d in the middle (such as
130+
# links to PRs in other repos)
131+
h = re.sub(r" #([0-9]+)", r' PR <a href="https://github.com/python/mypy/pull/\1">\1</a>', h)
129132
h = re.sub(r"\) \(PR", ", PR", h)
130133

131134
# Markdown links

misc/generate_changelog.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ def format_changelog_entry(c: CommitInfo) -> str:
145145
s += f" (#{c.pr_number})"
146146
s += f" ({c.author})"
147147
"""
148-
s = f" * {c.title} ({c.author}"
148+
title = c.title.removesuffix(".")
149+
s = f" * {title} ({c.author}"
149150
if c.pr_number:
150151
s += f", PR [{c.pr_number}](https://github.com/python/mypy/pull/{c.pr_number})"
151152
s += ")"

mypy/argmap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ def expand_actual_type(
220220
self.tuple_index += 1
221221
item = actual_type.items[self.tuple_index - 1]
222222
if isinstance(item, UnpackType) and not allow_unpack:
223-
# An upack item that doesn't have special handling, use upper bound as above.
223+
# An unpack item that doesn't have special handling, use upper bound as above.
224224
unpacked = get_proper_type(item.type)
225225
if isinstance(unpacked, TypeVarTupleType):
226226
fallback = get_proper_type(unpacked.upper_bound)

mypy/binder.py

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,22 @@ class Frame:
4141
"""A Frame represents a specific point in the execution of a program.
4242
It carries information about the current types of expressions at
4343
that point, arising either from assignments to those expressions
44-
or the result of isinstance checks. It also records whether it is
45-
possible to reach that point at all.
44+
or the result of isinstance checks and other type narrowing
45+
operations. It also records whether it is possible to reach that
46+
point at all.
47+
48+
We add a new frame wherenever there is a new scope or control flow
49+
branching.
4650
4751
This information is not copied into a new Frame when it is pushed
4852
onto the stack, so a given Frame only has information about types
4953
that were assigned in that frame.
54+
55+
Expressions are stored in dicts using 'literal hashes' as keys (type
56+
"Key"). These are hashable values derived from expression AST nodes
57+
(only those that can be narrowed). literal_hash(expr) is used to
58+
calculate the hashes. Note that this isn't directly related to literal
59+
types -- the concept predates literal types.
5060
"""
5161

5262
def __init__(self, id: int, conditional_frame: bool = False) -> None:
@@ -66,29 +76,29 @@ def __repr__(self) -> str:
6676
class ConditionalTypeBinder:
6777
"""Keep track of conditional types of variables.
6878
69-
NB: Variables are tracked by literal expression, so it is possible
70-
to confuse the binder; for example,
71-
72-
```
73-
class A:
74-
a: Union[int, str] = None
75-
x = A()
76-
lst = [x]
77-
reveal_type(x.a) # Union[int, str]
78-
x.a = 1
79-
reveal_type(x.a) # int
80-
reveal_type(lst[0].a) # Union[int, str]
81-
lst[0].a = 'a'
82-
reveal_type(x.a) # int
83-
reveal_type(lst[0].a) # str
84-
```
79+
NB: Variables are tracked by literal hashes of expressions, so it is
80+
possible to confuse the binder when there is aliasing. Example:
81+
82+
class A:
83+
a: int | str
84+
85+
x = A()
86+
lst = [x]
87+
reveal_type(x.a) # int | str
88+
x.a = 1
89+
reveal_type(x.a) # int
90+
reveal_type(lst[0].a) # int | str
91+
lst[0].a = 'a'
92+
reveal_type(x.a) # int
93+
reveal_type(lst[0].a) # str
8594
"""
8695

8796
# Stored assignments for situations with tuple/list lvalue and rvalue of union type.
8897
# This maps an expression to a list of bound types for every item in the union type.
8998
type_assignments: Assigns | None = None
9099

91100
def __init__(self) -> None:
101+
# Each frame gets an increasing, distinct id.
92102
self.next_id = 1
93103

94104
# The stack of frames currently used. These map
@@ -116,6 +126,7 @@ def __init__(self) -> None:
116126
# Whether the last pop changed the newly top frame on exit
117127
self.last_pop_changed = False
118128

129+
# These are used to track control flow in try statements and loops.
119130
self.try_frames: set[int] = set()
120131
self.break_frames: list[int] = []
121132
self.continue_frames: list[int] = []
@@ -151,6 +162,10 @@ def _get(self, key: Key, index: int = -1) -> CurrentType | None:
151162
return None
152163

153164
def put(self, expr: Expression, typ: Type, *, from_assignment: bool = True) -> None:
165+
"""Directly set the narrowed type of expression (if it supports it).
166+
167+
This is used for isinstance() etc. Assignments should go through assign_type().
168+
"""
154169
if not isinstance(expr, (IndexExpr, MemberExpr, NameExpr)):
155170
return
156171
if not literal(expr):
@@ -314,6 +329,13 @@ def accumulate_type_assignments(self) -> Iterator[Assigns]:
314329
self.type_assignments = old_assignments
315330

316331
def assign_type(self, expr: Expression, type: Type, declared_type: Type | None) -> None:
332+
"""Narrow type of expression through an assignment.
333+
334+
Do nothing if the expression doesn't support narrowing.
335+
336+
When not narrowing though an assignment (isinstance() etc.), use put()
337+
directly. This omits some special-casing logic for assignments.
338+
"""
317339
# We should erase last known value in binder, because if we are using it,
318340
# it means that the target is not final, and therefore can't hold a literal.
319341
type = remove_instance_last_known_values(type)
@@ -488,6 +510,11 @@ def top_frame_context(self) -> Iterator[Frame]:
488510

489511

490512
def get_declaration(expr: BindableExpression) -> Type | None:
513+
"""Get the declared or inferred type of a RefExpr expression.
514+
515+
Return None if there is no type or the expression is not a RefExpr.
516+
This can return None if the type hasn't been inferred yet.
517+
"""
491518
if isinstance(expr, RefExpr):
492519
if isinstance(expr.node, Var):
493520
type = expr.node.type

mypy/checker.py

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -703,50 +703,57 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
703703
def extract_callable_type(self, inner_type: Type | None, ctx: Context) -> CallableType | None:
704704
"""Get type as seen by an overload item caller."""
705705
inner_type = get_proper_type(inner_type)
706-
outer_type: CallableType | None = None
707-
if inner_type is not None and not isinstance(inner_type, AnyType):
708-
if isinstance(inner_type, TypeVarLikeType):
709-
inner_type = get_proper_type(inner_type.upper_bound)
710-
if isinstance(inner_type, TypeType):
711-
inner_type = get_proper_type(
712-
self.expr_checker.analyze_type_type_callee(inner_type.item, ctx)
713-
)
706+
outer_type: FunctionLike | None = None
707+
if inner_type is None or isinstance(inner_type, AnyType):
708+
return None
709+
if isinstance(inner_type, TypeVarLikeType):
710+
inner_type = get_proper_type(inner_type.upper_bound)
711+
if isinstance(inner_type, TypeType):
712+
inner_type = get_proper_type(
713+
self.expr_checker.analyze_type_type_callee(inner_type.item, ctx)
714+
)
714715

715-
if isinstance(inner_type, CallableType):
716-
outer_type = inner_type
717-
elif isinstance(inner_type, Instance):
718-
inner_call = get_proper_type(
719-
analyze_member_access(
720-
name="__call__",
721-
typ=inner_type,
722-
context=ctx,
723-
is_lvalue=False,
724-
is_super=False,
725-
is_operator=True,
726-
msg=self.msg,
727-
original_type=inner_type,
728-
chk=self,
729-
)
716+
if isinstance(inner_type, FunctionLike):
717+
outer_type = inner_type
718+
elif isinstance(inner_type, Instance):
719+
inner_call = get_proper_type(
720+
analyze_member_access(
721+
name="__call__",
722+
typ=inner_type,
723+
context=ctx,
724+
is_lvalue=False,
725+
is_super=False,
726+
is_operator=True,
727+
msg=self.msg,
728+
original_type=inner_type,
729+
chk=self,
730730
)
731-
if isinstance(inner_call, CallableType):
732-
outer_type = inner_call
733-
elif isinstance(inner_type, UnionType):
734-
union_type = make_simplified_union(inner_type.items)
735-
if isinstance(union_type, UnionType):
736-
items = []
737-
for item in union_type.items:
738-
callable_item = self.extract_callable_type(item, ctx)
739-
if callable_item is None:
740-
break
741-
items.append(callable_item)
742-
else:
743-
joined_type = get_proper_type(join.join_type_list(items))
744-
if isinstance(joined_type, CallableType):
745-
outer_type = joined_type
731+
)
732+
if isinstance(inner_call, FunctionLike):
733+
outer_type = inner_call
734+
elif isinstance(inner_type, UnionType):
735+
union_type = make_simplified_union(inner_type.items)
736+
if isinstance(union_type, UnionType):
737+
items = []
738+
for item in union_type.items:
739+
callable_item = self.extract_callable_type(item, ctx)
740+
if callable_item is None:
741+
break
742+
items.append(callable_item)
746743
else:
747-
return self.extract_callable_type(union_type, ctx)
748-
if outer_type is None:
749-
self.msg.not_callable(inner_type, ctx)
744+
joined_type = get_proper_type(join.join_type_list(items))
745+
if isinstance(joined_type, FunctionLike):
746+
outer_type = joined_type
747+
else:
748+
return self.extract_callable_type(union_type, ctx)
749+
750+
if outer_type is None:
751+
self.msg.not_callable(inner_type, ctx)
752+
return None
753+
if isinstance(outer_type, Overloaded):
754+
return None
755+
756+
assert isinstance(outer_type, CallableType)
750757
return outer_type
751758

752759
def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:

mypy/checkexpr.py

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
import mypy.errorcodes as codes
1616
from mypy import applytype, erasetype, join, message_registry, nodes, operators, types
1717
from mypy.argmap import ArgTypeExpander, map_actuals_to_formals, map_formals_to_actuals
18-
from mypy.checkmember import analyze_member_access, freeze_all_type_vars, type_object_type
18+
from mypy.checkmember import (
19+
analyze_member_access,
20+
freeze_all_type_vars,
21+
type_object_type,
22+
typeddict_callable,
23+
)
1924
from mypy.checkstrformat import StringFormatterChecker
2025
from mypy.erasetype import erase_type, remove_instance_last_known_values, replace_meta_vars
2126
from mypy.errors import ErrorWatcher, report_internal_error
@@ -955,20 +960,7 @@ def typeddict_callable(self, info: TypeInfo) -> CallableType:
955960
Note it is not safe to move this to type_object_type() since it will crash
956961
on plugin-generated TypedDicts, that may not have the special_alias.
957962
"""
958-
assert info.special_alias is not None
959-
target = info.special_alias.target
960-
assert isinstance(target, ProperType) and isinstance(target, TypedDictType)
961-
expected_types = list(target.items.values())
962-
kinds = [ArgKind.ARG_NAMED] * len(expected_types)
963-
names = list(target.items.keys())
964-
return CallableType(
965-
expected_types,
966-
kinds,
967-
names,
968-
target,
969-
self.named_type("builtins.type"),
970-
variables=info.defn.type_vars,
971-
)
963+
return typeddict_callable(info, self.named_type)
972964

973965
def typeddict_callable_from_context(self, callee: TypedDictType) -> CallableType:
974966
return CallableType(
@@ -3192,7 +3184,7 @@ def combine_function_signatures(self, types: list[ProperType]) -> AnyType | Call
31923184
new_type_narrowers.append(target.type_is)
31933185

31943186
if new_type_guards and new_type_narrowers:
3195-
# They cannot be definined at the same time,
3187+
# They cannot be defined at the same time,
31963188
# declaring this function as too complex!
31973189
too_complex = True
31983190
union_type_guard = None

0 commit comments

Comments
 (0)