Skip to content

Commit 45f82df

Browse files
committed
Merge remote-tracking branch 'upstream/master' into fix-nested-deferral
2 parents 7f2cc68 + 5fcca77 commit 45f82df

21 files changed

+252
-67
lines changed

.github/workflows/test.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
# the oldest and newest supported Python versions
3434
- name: Test suite with py39-ubuntu, mypyc-compiled
3535
python: '3.9'
36-
os: ubuntu-22.04-arm
36+
os: ubuntu-24.04-arm
3737
toxenv: py
3838
tox_extra_args: "-n 4"
3939
test_mypyc: true
@@ -44,31 +44,31 @@ jobs:
4444
tox_extra_args: "-n 4"
4545
- name: Test suite with py310-ubuntu
4646
python: '3.10'
47-
os: ubuntu-22.04-arm
47+
os: ubuntu-24.04-arm
4848
toxenv: py
4949
tox_extra_args: "-n 4"
5050
- name: Test suite with py311-ubuntu, mypyc-compiled
5151
python: '3.11'
52-
os: ubuntu-22.04-arm
52+
os: ubuntu-24.04-arm
5353
toxenv: py
5454
tox_extra_args: "-n 4"
5555
test_mypyc: true
5656
- name: Test suite with py312-ubuntu, mypyc-compiled
5757
python: '3.12'
58-
os: ubuntu-22.04-arm
58+
os: ubuntu-24.04-arm
5959
toxenv: py
6060
tox_extra_args: "-n 4"
6161
test_mypyc: true
6262
- name: Test suite with py313-ubuntu, mypyc-compiled
6363
python: '3.13'
64-
os: ubuntu-22.04-arm
64+
os: ubuntu-24.04-arm
6565
toxenv: py
6666
tox_extra_args: "-n 4"
6767
test_mypyc: true
6868

6969
# - name: Test suite with py314-dev-ubuntu
7070
# python: '3.14-dev'
71-
# os: ubuntu-22.04-arm
71+
# os: ubuntu-24.04-arm
7272
# toxenv: py
7373
# tox_extra_args: "-n 4"
7474
# allow_failure: true

mypy/semanal_main.py

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626

2727
from __future__ import annotations
2828

29+
from collections.abc import Iterator
2930
from contextlib import nullcontext
31+
from itertools import groupby
3032
from typing import TYPE_CHECKING, Callable, Final, Optional, Union
3133
from typing_extensions import TypeAlias as _TypeAlias
3234

@@ -232,26 +234,66 @@ def process_top_levels(graph: Graph, scc: list[str], patches: Patches) -> None:
232234
final_iteration = not any_progress
233235

234236

237+
def order_by_subclassing(targets: list[FullTargetInfo]) -> Iterator[FullTargetInfo]:
238+
"""Make sure that superclass methods are always processed before subclass methods.
239+
240+
This algorithm is not very optimal, but it is simple and should work well for lists
241+
that are already almost correctly ordered.
242+
"""
243+
244+
# First, group the targets by their TypeInfo (since targets are sorted by line,
245+
# we know that each TypeInfo will appear as group key only once).
246+
grouped = [(k, list(g)) for k, g in groupby(targets, key=lambda x: x[3])]
247+
remaining_infos = {info for info, _ in grouped if info is not None}
248+
249+
next_group = 0
250+
while grouped:
251+
if next_group >= len(grouped):
252+
# This should never happen, if there is an MRO cycle, it should be reported
253+
# and fixed during top-level processing.
254+
raise ValueError("Cannot order method targets by MRO")
255+
next_info, group = grouped[next_group]
256+
if next_info is None:
257+
# Trivial case, not methods but functions, process them straight away.
258+
yield from group
259+
grouped.pop(next_group)
260+
continue
261+
if any(parent in remaining_infos for parent in next_info.mro[1:]):
262+
# We cannot process this method group yet, try a next one.
263+
next_group += 1
264+
continue
265+
yield from group
266+
grouped.pop(next_group)
267+
remaining_infos.discard(next_info)
268+
# Each time after processing a method group we should retry from start,
269+
# since there may be some groups that are not blocked on parents anymore.
270+
next_group = 0
271+
272+
235273
def process_functions(graph: Graph, scc: list[str], patches: Patches) -> None:
236274
# Process functions.
275+
all_targets = []
237276
for module in scc:
238277
tree = graph[module].tree
239278
assert tree is not None
240-
analyzer = graph[module].manager.semantic_analyzer
241279
# In principle, functions can be processed in arbitrary order,
242280
# but _methods_ must be processed in the order they are defined,
243281
# because some features (most notably partial types) depend on
244282
# order of definitions on self.
245283
#
246284
# There can be multiple generated methods per line. Use target
247-
# name as the second sort key to get a repeatable sort order on
248-
# Python 3.5, which doesn't preserve dictionary order.
285+
# name as the second sort key to get a repeatable sort order.
249286
targets = sorted(get_all_leaf_targets(tree), key=lambda x: (x[1].line, x[0]))
250-
for target, node, active_type in targets:
251-
assert isinstance(node, (FuncDef, OverloadedFuncDef, Decorator))
252-
process_top_level_function(
253-
analyzer, graph[module], module, target, node, active_type, patches
254-
)
287+
all_targets.extend(
288+
[(module, target, node, active_type) for target, node, active_type in targets]
289+
)
290+
291+
for module, target, node, active_type in order_by_subclassing(all_targets):
292+
analyzer = graph[module].manager.semantic_analyzer
293+
assert isinstance(node, (FuncDef, OverloadedFuncDef, Decorator))
294+
process_top_level_function(
295+
analyzer, graph[module], module, target, node, active_type, patches
296+
)
255297

256298

257299
def process_top_level_function(
@@ -308,6 +350,11 @@ def process_top_level_function(
308350
str, Union[MypyFile, FuncDef, OverloadedFuncDef, Decorator], Optional[TypeInfo]
309351
]
310352

353+
# Same as above but includes module as first item.
354+
FullTargetInfo: _TypeAlias = tuple[
355+
str, str, Union[MypyFile, FuncDef, OverloadedFuncDef, Decorator], Optional[TypeInfo]
356+
]
357+
311358

312359
def get_all_leaf_targets(file: MypyFile) -> list[TargetInfo]:
313360
"""Return all leaf targets in a symbol table (module-level and methods)."""

mypy/stubdoc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
Sig: _TypeAlias = tuple[str, str]
2222

2323

24-
_TYPE_RE: Final = re.compile(r"^[a-zA-Z_][\w\[\], .\"\']*(\.[a-zA-Z_][\w\[\], ]*)*$")
24+
_TYPE_RE: Final = re.compile(r"^[a-zA-Z_][\w\[\], .\"\'|]*(\.[a-zA-Z_][\w\[\], ]*)*$")
2525
_ARG_NAME_RE: Final = re.compile(r"\**[A-Za-z_][A-Za-z0-9_]*$")
2626

2727

mypy/test/teststubgen.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1405,6 +1405,9 @@ def test_is_valid_type(self) -> None:
14051405
assert is_valid_type("Literal[True]")
14061406
assert is_valid_type("Literal[Color.RED]")
14071407
assert is_valid_type("Literal[None]")
1408+
assert is_valid_type("str | int")
1409+
assert is_valid_type("dict[str, int] | int")
1410+
assert is_valid_type("tuple[str, ...]")
14081411
assert is_valid_type(
14091412
'Literal[26, 0x1A, "hello world", b"hello world", u"hello world", True, Color.RED, None]'
14101413
)

mypy/typeshed/stdlib/_frozen_importlib_external.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ else:
2626

2727
MAGIC_NUMBER: bytes
2828

29-
def cache_from_source(path: str, debug_override: bool | None = None, *, optimization: Any | None = None) -> str: ...
30-
def source_from_cache(path: str) -> str: ...
29+
def cache_from_source(path: StrPath, debug_override: bool | None = None, *, optimization: Any | None = None) -> str: ...
30+
def source_from_cache(path: StrPath) -> str: ...
3131
def decode_source(source_bytes: ReadableBuffer) -> str: ...
3232
def spec_from_file_location(
3333
name: str,

mypy/typeshed/stdlib/codecs.pyi

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ from _codecs import *
33
from _typeshed import ReadableBuffer
44
from abc import abstractmethod
55
from collections.abc import Callable, Generator, Iterable
6-
from typing import Any, BinaryIO, ClassVar, Final, Literal, Protocol, TextIO
7-
from typing_extensions import Self
6+
from typing import Any, BinaryIO, ClassVar, Final, Literal, Protocol, TextIO, overload
7+
from typing_extensions import Self, TypeAlias
88

99
__all__ = [
1010
"register",
@@ -58,6 +58,21 @@ BOM32_LE: Final = b"\xff\xfe"
5858
BOM64_BE: Final = b"\x00\x00\xfe\xff"
5959
BOM64_LE: Final = b"\xff\xfe\x00\x00"
6060

61+
_BufferedEncoding: TypeAlias = Literal[
62+
"idna",
63+
"raw-unicode-escape",
64+
"unicode-escape",
65+
"utf-16",
66+
"utf-16-be",
67+
"utf-16-le",
68+
"utf-32",
69+
"utf-32-be",
70+
"utf-32-le",
71+
"utf-7",
72+
"utf-8",
73+
"utf-8-sig",
74+
]
75+
6176
class _WritableStream(Protocol):
6277
def write(self, data: bytes, /) -> object: ...
6378
def seek(self, offset: int, whence: int, /) -> object: ...
@@ -94,6 +109,9 @@ class _IncrementalEncoder(Protocol):
94109
class _IncrementalDecoder(Protocol):
95110
def __call__(self, errors: str = ...) -> IncrementalDecoder: ...
96111

112+
class _BufferedIncrementalDecoder(Protocol):
113+
def __call__(self, errors: str = ...) -> BufferedIncrementalDecoder: ...
114+
97115
class CodecInfo(tuple[_Encoder, _Decoder, _StreamReader, _StreamWriter]):
98116
_is_text_encoding: bool
99117
@property
@@ -125,6 +143,9 @@ class CodecInfo(tuple[_Encoder, _Decoder, _StreamReader, _StreamWriter]):
125143
def getencoder(encoding: str) -> _Encoder: ...
126144
def getdecoder(encoding: str) -> _Decoder: ...
127145
def getincrementalencoder(encoding: str) -> _IncrementalEncoder: ...
146+
@overload
147+
def getincrementaldecoder(encoding: _BufferedEncoding) -> _BufferedIncrementalDecoder: ...
148+
@overload
128149
def getincrementaldecoder(encoding: str) -> _IncrementalDecoder: ...
129150
def getreader(encoding: str) -> _StreamReader: ...
130151
def getwriter(encoding: str) -> _StreamWriter: ...

mypy/typeshed/stdlib/compileall.pyi

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ if sys.version_info >= (3, 10):
2525
prependdir: StrPath | None = None,
2626
limit_sl_dest: StrPath | None = None,
2727
hardlink_dupes: bool = False,
28-
) -> int: ...
28+
) -> bool: ...
2929
def compile_file(
3030
fullname: StrPath,
3131
ddir: StrPath | None = None,
@@ -40,7 +40,7 @@ if sys.version_info >= (3, 10):
4040
prependdir: StrPath | None = None,
4141
limit_sl_dest: StrPath | None = None,
4242
hardlink_dupes: bool = False,
43-
) -> int: ...
43+
) -> bool: ...
4444

4545
elif sys.version_info >= (3, 9):
4646
def compile_dir(
@@ -59,7 +59,7 @@ elif sys.version_info >= (3, 9):
5959
prependdir: StrPath | None = None,
6060
limit_sl_dest: StrPath | None = None,
6161
hardlink_dupes: bool = False,
62-
) -> int: ...
62+
) -> bool: ...
6363
def compile_file(
6464
fullname: StrPath,
6565
ddir: StrPath | None = None,
@@ -74,7 +74,7 @@ elif sys.version_info >= (3, 9):
7474
prependdir: StrPath | None = None,
7575
limit_sl_dest: StrPath | None = None,
7676
hardlink_dupes: bool = False,
77-
) -> int: ...
77+
) -> bool: ...
7878

7979
else:
8080
def compile_dir(
@@ -88,7 +88,7 @@ else:
8888
optimize: int = -1,
8989
workers: int = 1,
9090
invalidation_mode: PycInvalidationMode | None = None,
91-
) -> int: ...
91+
) -> bool: ...
9292
def compile_file(
9393
fullname: StrPath,
9494
ddir: StrPath | None = None,
@@ -98,7 +98,7 @@ else:
9898
legacy: bool = False,
9999
optimize: int = -1,
100100
invalidation_mode: PycInvalidationMode | None = None,
101-
) -> int: ...
101+
) -> bool: ...
102102

103103
def compile_path(
104104
skip_curdir: bool = ...,
@@ -108,4 +108,4 @@ def compile_path(
108108
legacy: bool = False,
109109
optimize: int = -1,
110110
invalidation_mode: PycInvalidationMode | None = None,
111-
) -> int: ...
111+
) -> bool: ...

mypy/typeshed/stdlib/email/__init__.pyi

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from collections.abc import Callable
22
from email.message import Message
3-
from email.policy import Policy
4-
from typing import IO
3+
from email.policy import Policy, _MessageT
4+
from typing import IO, overload
55
from typing_extensions import TypeAlias
66

77
# At runtime, listing submodules in __all__ without them being imported is
@@ -31,7 +31,29 @@ __all__ = [ # noqa: F822 # Undefined names in __all__
3131
_ParamType: TypeAlias = str | tuple[str | None, str | None, str] # noqa: Y047
3232
_ParamsType: TypeAlias = str | None | tuple[str, str | None, str] # noqa: Y047
3333

34-
def message_from_string(s: str, _class: Callable[[], Message] = ..., *, policy: Policy = ...) -> Message: ...
35-
def message_from_bytes(s: bytes | bytearray, _class: Callable[[], Message] = ..., *, policy: Policy = ...) -> Message: ...
36-
def message_from_file(fp: IO[str], _class: Callable[[], Message] = ..., *, policy: Policy = ...) -> Message: ...
37-
def message_from_binary_file(fp: IO[bytes], _class: Callable[[], Message] = ..., *, policy: Policy = ...) -> Message: ...
34+
@overload
35+
def message_from_string(s: str) -> Message: ...
36+
@overload
37+
def message_from_string(s: str, _class: Callable[[], _MessageT]) -> _MessageT: ...
38+
@overload
39+
def message_from_string(s: str, _class: Callable[[], _MessageT] = ..., *, policy: Policy[_MessageT]) -> _MessageT: ...
40+
@overload
41+
def message_from_bytes(s: bytes | bytearray) -> Message: ...
42+
@overload
43+
def message_from_bytes(s: bytes | bytearray, _class: Callable[[], _MessageT]) -> _MessageT: ...
44+
@overload
45+
def message_from_bytes(
46+
s: bytes | bytearray, _class: Callable[[], _MessageT] = ..., *, policy: Policy[_MessageT]
47+
) -> _MessageT: ...
48+
@overload
49+
def message_from_file(fp: IO[str]) -> Message: ...
50+
@overload
51+
def message_from_file(fp: IO[str], _class: Callable[[], _MessageT]) -> _MessageT: ...
52+
@overload
53+
def message_from_file(fp: IO[str], _class: Callable[[], _MessageT] = ..., *, policy: Policy[_MessageT]) -> _MessageT: ...
54+
@overload
55+
def message_from_binary_file(fp: IO[bytes]) -> Message: ...
56+
@overload
57+
def message_from_binary_file(fp: IO[bytes], _class: Callable[[], _MessageT]) -> _MessageT: ...
58+
@overload
59+
def message_from_binary_file(fp: IO[bytes], _class: Callable[[], _MessageT] = ..., *, policy: Policy[_MessageT]) -> _MessageT: ...
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
from email.message import Message
21
from email.mime.nonmultipart import MIMENonMultipart
3-
from email.policy import Policy
2+
from email.policy import Policy, _MessageT
43

54
__all__ = ["MIMEMessage"]
65

76
class MIMEMessage(MIMENonMultipart):
8-
def __init__(self, _msg: Message, _subtype: str = "rfc822", *, policy: Policy | None = None) -> None: ...
7+
def __init__(self, _msg: _MessageT, _subtype: str = "rfc822", *, policy: Policy[_MessageT] | None = None) -> None: ...
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
from collections.abc import Sequence
22
from email import _ParamsType
3-
from email.message import Message
43
from email.mime.base import MIMEBase
5-
from email.policy import Policy
4+
from email.policy import Policy, _MessageT
65

76
__all__ = ["MIMEMultipart"]
87

@@ -11,8 +10,8 @@ class MIMEMultipart(MIMEBase):
1110
self,
1211
_subtype: str = "mixed",
1312
boundary: str | None = None,
14-
_subparts: Sequence[Message] | None = None,
13+
_subparts: Sequence[_MessageT] | None = None,
1514
*,
16-
policy: Policy | None = None,
15+
policy: Policy[_MessageT] | None = None,
1716
**_params: _ParamsType,
1817
) -> None: ...

0 commit comments

Comments
 (0)