Skip to content

Commit 47bcf75

Browse files
committed
Merge remote-tracking branch 'upstream/master' into intf-impl-parallel
2 parents 1dd01db + fed3593 commit 47bcf75

33 files changed

Lines changed: 719 additions & 179 deletions

.github/workflows/test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ jobs:
139139
timeout-minutes: 60
140140
env:
141141
TOX_SKIP_MISSING_INTERPRETERS: False
142+
VIRTUALENV_SYSTEM_SITE_PACKAGES: ${{ matrix.test_mypyc && 1 || 0 }}
142143
# Rich (pip) -- Disable color for windows + pytest
143144
FORCE_COLOR: ${{ !(startsWith(matrix.os, 'windows-') && startsWith(matrix.toxenv, 'py')) && 1 || 0 }}
144145
# Tox
@@ -209,8 +210,10 @@ jobs:
209210
210211
- name: Compiled with mypyc
211212
if: ${{ matrix.test_mypyc }}
213+
# Use local version of librt during self-compilation in tests.
212214
run: |
213215
pip install -r test-requirements.txt
216+
pip install -U mypyc/lib-rt
214217
CC=clang MYPYC_OPT_LEVEL=0 MYPY_USE_MYPYC=1 pip install -e .
215218
216219
- name: Setup tox environment
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
From d88a4b774fc59ecd0f2d0fdda8e0e61092adafd3 Mon Sep 17 00:00:00 2001
2+
From: Ivan Levkivskyi <levkivskyi@gmail.com>
3+
Date: Wed, 8 Apr 2026 00:01:44 +0100
4+
Subject: [PATCH] Revert dict.__or__ typeshed change
5+
6+
---
7+
mypy/typeshed/stdlib/builtins.pyi | 6 ++++++
8+
1 file changed, 6 insertions(+)
9+
10+
diff --git a/mypy/typeshed/stdlib/builtins.pyi b/mypy/typeshed/stdlib/builtins.pyi
11+
index 674142d70..25b21ba97 100644
12+
--- a/mypy/typeshed/stdlib/builtins.pyi
13+
+++ b/mypy/typeshed/stdlib/builtins.pyi
14+
@@ -1142,7 +1142,13 @@ class dict(MutableMapping[_KT, _VT]):
15+
def __reversed__(self) -> Iterator[_KT]: ...
16+
__hash__: ClassVar[None] # type: ignore[assignment]
17+
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
18+
+ @overload
19+
+ def __or__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ...
20+
+ @overload
21+
def __or__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ...
22+
+ @overload
23+
+ def __ror__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ...
24+
+ @overload
25+
def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ...
26+
# dict.__ior__ should be kept roughly in line with MutableMapping.update()
27+
@overload # type: ignore[misc]
28+
--
29+
2.25.1
30+

mypy-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ typing_extensions>=4.6.0
44
mypy_extensions>=1.0.0
55
pathspec>=1.0.0
66
tomli>=1.1.0; python_version<'3.11'
7-
librt>=0.8.0; platform_python_implementation != 'PyPy'
7+
librt>=0.9.0; platform_python_implementation != 'PyPy'

mypy/build.py

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@
145145

146146
from mypy import errorcodes as codes
147147
from mypy.config_parser import get_config_module_names, parse_mypy_comments
148-
from mypy.fixup import fixup_module
148+
from mypy.fixer_state import fixer_state
149+
from mypy.fixup import NodeFixer
149150
from mypy.freetree import free_tree
150151
from mypy.fscache import FileSystemCache
151152
from mypy.known_modules import get_known_modules, reset_known_modules_cache
@@ -814,6 +815,10 @@ def __init__(
814815
self.options = options
815816
self.version_id = version_id
816817
self.modules: dict[str, MypyFile] = {}
818+
# Share same modules dictionary with the global fixer state.
819+
# We need to set allow_missing when doing a fine-grained cache
820+
# load because we need to gracefully handle missing modules.
821+
fixer_state.node_fixer = NodeFixer(self.modules, self.options.use_fine_grained_cache)
817822
self.import_map: dict[str, set[str]] = {}
818823
self.missing_modules: dict[str, int] = {}
819824
self.fg_deps_meta: dict[str, FgDepMeta] = {}
@@ -879,7 +884,7 @@ def __init__(
879884
]
880885
)
881886

882-
self.metastore = create_metastore(options, parallel_worker)
887+
self.metastore = create_metastore(options)
883888

884889
# a mapping from source files to their corresponding shadow files
885890
# for efficient lookup
@@ -1217,7 +1222,9 @@ def wait_for_done_workers(
12171222
done_sccs = []
12181223
results = {}
12191224
for idx in ready_to_read([w.conn for w in self.workers], WORKER_DONE_TIMEOUT):
1220-
data = SccResponseMessage.read(receive(self.workers[idx].conn))
1225+
buf = receive(self.workers[idx].conn)
1226+
assert read_tag(buf) == SCC_RESPONSE_MESSAGE
1227+
data = SccResponseMessage.read(buf)
12211228
if not data.is_interface:
12221229
# Mark worker as free after it finished checking implementation.
12231230
self.free_workers.add(idx)
@@ -1622,13 +1629,10 @@ def exclude_from_backups(target_dir: str) -> None:
16221629
pass
16231630

16241631

1625-
def create_metastore(options: Options, parallel_worker: bool = False) -> MetadataStore:
1632+
def create_metastore(options: Options) -> MetadataStore:
16261633
"""Create the appropriate metadata store."""
16271634
if options.sqlite_cache:
1628-
# We use this flag in both coordinator and workers to speed up commits,
1629-
# see mypy.metastore.connect_db() for details.
1630-
sync_off = options.num_workers > 0 or parallel_worker
1631-
mds: MetadataStore = SqliteMetadataStore(_cache_dir_prefix(options), sync_off=sync_off)
1635+
mds: MetadataStore = SqliteMetadataStore(_cache_dir_prefix(options))
16321636
else:
16331637
mds = FilesystemMetadataStore(_cache_dir_prefix(options))
16341638
return mds
@@ -2836,9 +2840,21 @@ def load_tree(self, temporary: bool = False) -> None:
28362840

28372841
def fix_cross_refs(self) -> None:
28382842
assert self.tree is not None, "Internal error: method must be called on parsed file only"
2839-
# We need to set allow_missing when doing a fine-grained cache
2840-
# load because we need to gracefully handle missing modules.
2841-
fixup_module(self.tree, self.manager.modules, self.options.use_fine_grained_cache)
2843+
# Do initial lightweight pass fixing TypeInfos and module cross-references.
2844+
assert fixer_state.node_fixer is not None
2845+
fixer_state.node_fixer.visit_symbol_table(self.tree.names)
2846+
type_fixer = fixer_state.node_fixer.type_fixer
2847+
# Eagerly fix shared instances, before they are used by named_type() calls.
2848+
if instance_cache.str_type is not None:
2849+
instance_cache.str_type.accept(type_fixer)
2850+
if instance_cache.function_type is not None:
2851+
instance_cache.function_type.accept(type_fixer)
2852+
if instance_cache.int_type is not None:
2853+
instance_cache.int_type.accept(type_fixer)
2854+
if instance_cache.bool_type is not None:
2855+
instance_cache.bool_type.accept(type_fixer)
2856+
if instance_cache.object_type is not None:
2857+
instance_cache.object_type.accept(type_fixer)
28422858

28432859
# Methods for processing modules from source code.
28442860

@@ -4183,7 +4199,8 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
41834199
graph_message.write(buf)
41844200
graph_data = buf.getvalue()
41854201
for worker in manager.workers:
4186-
AckMessage.read(receive(worker.conn))
4202+
buf = receive(worker.conn)
4203+
assert read_tag(buf) == ACK_MESSAGE
41874204
worker.conn.write_bytes(graph_data)
41884205

41894206
sccs = sorted_components(graph)
@@ -4203,10 +4220,12 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
42034220
sccs_message.write(buf)
42044221
sccs_data = buf.getvalue()
42054222
for worker in manager.workers:
4206-
AckMessage.read(receive(worker.conn))
4223+
buf = receive(worker.conn)
4224+
assert read_tag(buf) == ACK_MESSAGE
42074225
worker.conn.write_bytes(sccs_data)
42084226
for worker in manager.workers:
4209-
AckMessage.read(receive(worker.conn))
4227+
buf = receive(worker.conn)
4228+
assert read_tag(buf) == ACK_MESSAGE
42104229

42114230
manager.free_workers = set(range(manager.options.num_workers))
42124231

@@ -4785,7 +4804,6 @@ class AckMessage(IPCMessage):
47854804

47864805
@classmethod
47874806
def read(cls, buf: ReadBuffer) -> AckMessage:
4788-
assert read_tag(buf) == ACK_MESSAGE
47894807
return AckMessage()
47904808

47914809
def write(self, buf: WriteBuffer) -> None:
@@ -4812,7 +4830,6 @@ def __init__(
48124830

48134831
@classmethod
48144832
def read(cls, buf: ReadBuffer) -> SccRequestMessage:
4815-
assert read_tag(buf) == SCC_REQUEST_MESSAGE
48164833
return SccRequestMessage(
48174834
scc_id=read_int_opt(buf),
48184835
import_errors={
@@ -4897,7 +4914,6 @@ def __init__(
48974914

48984915
@classmethod
48994916
def read(cls, buf: ReadBuffer) -> SccResponseMessage:
4900-
assert read_tag(buf) == SCC_RESPONSE_MESSAGE
49014917
scc_id = read_int(buf)
49024918
is_interface = read_bool(buf)
49034919
tag = read_tag(buf)
@@ -4943,7 +4959,6 @@ def __init__(self, *, sources: list[BuildSource]) -> None:
49434959

49444960
@classmethod
49454961
def read(cls, buf: ReadBuffer) -> SourcesDataMessage:
4946-
assert read_tag(buf) == SOURCES_DATA_MESSAGE
49474962
sources = [
49484963
BuildSource(
49494964
read_str_opt(buf),
@@ -4975,7 +4990,6 @@ def __init__(self, *, sccs: list[SCC]) -> None:
49754990

49764991
@classmethod
49774992
def read(cls, buf: ReadBuffer) -> SccsDataMessage:
4978-
assert read_tag(buf) == SCCS_DATA_MESSAGE
49794993
sccs = [
49804994
SCC(set(read_str_list(buf)), read_int(buf), read_int_list(buf))
49814995
for _ in range(read_int_bare(buf))
@@ -5003,7 +5017,6 @@ def __init__(self, *, graph: Graph, missing_modules: dict[str, int]) -> None:
50035017
@classmethod
50045018
def read(cls, buf: ReadBuffer, manager: BuildManager | None = None) -> GraphMessage:
50055019
assert manager is not None
5006-
assert read_tag(buf) == GRAPH_MESSAGE
50075020
graph = {read_str_bare(buf): State.read(buf, manager) for _ in range(read_int_bare(buf))}
50085021
missing_modules = {read_str_bare(buf): read_int(buf) for _ in range(read_int_bare(buf))}
50095022
message = GraphMessage(graph=graph, missing_modules=missing_modules)

mypy/build_worker/worker.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,15 @@
2626
from typing import NamedTuple
2727

2828
from librt.base64 import b64decode
29+
from librt.internal import ReadBuffer, read_tag
2930

3031
from mypy import util
3132
from mypy.build import (
33+
GRAPH_MESSAGE,
3234
SCC,
35+
SCC_REQUEST_MESSAGE,
36+
SCCS_DATA_MESSAGE,
37+
SOURCES_DATA_MESSAGE,
3338
AckMessage,
3439
BuildManager,
3540
Graph,
@@ -42,6 +47,7 @@
4247
process_stale_scc_implementation,
4348
process_stale_scc_interface,
4449
)
50+
from mypy.cache import Tag, read_int_opt
4551
from mypy.defaults import RECURSION_LIMIT, WORKER_CONNECTION_TIMEOUT
4652
from mypy.errors import CompileError, ErrorInfo, Errors, report_internal_error
4753
from mypy.fscache import FileSystemCache
@@ -116,21 +122,37 @@ def main(argv: list[str]) -> None:
116122
util.hard_exit(0)
117123

118124

125+
def should_shutdown(buf: ReadBuffer, expected_tag: Tag) -> bool:
126+
"""Check if the message is a shutdown request."""
127+
tag = read_tag(buf)
128+
if tag == SCC_REQUEST_MESSAGE:
129+
assert read_int_opt(buf) is None
130+
return True
131+
assert tag == expected_tag, f"Unexpected tag: {tag}"
132+
return False
133+
134+
119135
def serve(server: IPCServer, ctx: ServerContext) -> None:
120136
"""Main server loop of the worker.
121137
122138
Receive initial state from the coordinator, then process each
123139
SCC checking request and reply to client (coordinator). See module
124140
docstring for more details on the protocol.
125141
"""
126-
sources = SourcesDataMessage.read(receive(server)).sources
142+
buf = receive(server)
143+
if should_shutdown(buf, SOURCES_DATA_MESSAGE):
144+
return
145+
sources = SourcesDataMessage.read(buf).sources
127146
manager = setup_worker_manager(sources, ctx)
128147
if manager is None:
129148
return
130149

131150
# Notify coordinator we are done with setup.
132151
send(server, AckMessage())
133-
graph_data = GraphMessage.read(receive(server), manager)
152+
buf = receive(server)
153+
if should_shutdown(buf, GRAPH_MESSAGE):
154+
return
155+
graph_data = GraphMessage.read(buf, manager)
134156
# Update some manager data in-place as it has been passed to semantic analyzer.
135157
manager.missing_modules |= graph_data.missing_modules
136158
graph = graph_data.graph
@@ -141,14 +163,19 @@ def serve(server: IPCServer, ctx: ServerContext) -> None:
141163

142164
# Notify coordinator we are ready to receive computed graph SCC structure.
143165
send(server, AckMessage())
144-
sccs = SccsDataMessage.read(receive(server)).sccs
166+
buf = receive(server)
167+
if should_shutdown(buf, SCCS_DATA_MESSAGE):
168+
return
169+
sccs = SccsDataMessage.read(buf).sccs
145170
manager.scc_by_id = {scc.id: scc for scc in sccs}
146171
manager.top_order = [scc.id for scc in sccs]
147172

148173
# Notify coordinator we are ready to start processing SCCs.
149174
send(server, AckMessage())
150175
while True:
151-
scc_message = SccRequestMessage.read(receive(server))
176+
buf = receive(server)
177+
assert read_tag(buf) == SCC_REQUEST_MESSAGE
178+
scc_message = SccRequestMessage.read(buf)
152179
scc_id = scc_message.scc_id
153180
if scc_id is None:
154181
manager.dump_stats()

mypy/cache.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ def read(cls, data: ReadBuffer) -> CacheMetaEx | None:
298298
# Always use this type alias to refer to type tags.
299299
Tag = u8
300300

301+
# Note: all tags should be kept in sync with lib-rt/internal/librt_internal.c.
301302
# Primitives.
302303
LITERAL_FALSE: Final[Tag] = 0
303304
LITERAL_TRUE: Final[Tag] = 1
@@ -323,6 +324,7 @@ def read(cls, data: ReadBuffer) -> CacheMetaEx | None:
323324
# Four integers representing source file (line, column) range.
324325
LOCATION: Final[Tag] = 152
325326

327+
RESERVED: Final[Tag] = 254
326328
END_TAG: Final[Tag] = 255
327329

328330

mypy/checker.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6903,6 +6903,14 @@ def narrow_type_by_identity_equality(
69036903
expr_in_type_expr = type_expr.expr
69046904
else:
69056905
continue
6906+
6907+
p_expr_type = get_proper_type(operand_types[i])
6908+
if isinstance(p_expr_type, TypeType) and isinstance(p_expr_type.item, TypeVarType):
6909+
# This mirrors logic in comparison_type_narrowing_helper
6910+
# In theory, this is like `i not in narrowable_indices`, except that
6911+
# narrowable_indices filters all type(x) narrowing as it's a call
6912+
continue
6913+
69066914
for j in expr_indices:
69076915
if i == j:
69086916
continue
@@ -8627,15 +8635,19 @@ def and_conditional_maps(m1: TypeMap, m2: TypeMap, *, use_meet: bool = False) ->
86278635
the truth of e2.
86288636
"""
86298637
# Both conditions can be true; combine the information. Anything
8630-
# we learn from either conditions' truth is valid. If the same
8631-
# expression's type is refined by both conditions, we somewhat
8632-
# arbitrarily give precedence to m2 unless m1 value is Any.
8633-
# In the future, we could use an intersection type or meet_types().
8638+
# we learn from either conditions' truth is valid.
8639+
# If the same expression's type is refined by both conditions and use_meet=False, we somewhat
8640+
# arbitrarily give precedence to m2 unless m2's value is Any or m1's value is Never.
86348641
result = m2.copy()
8635-
m2_keys = {literal_hash(n2) for n2 in m2}
8642+
m2_exprs = {literal_hash(n2): n2 for n2 in m2}
86368643
for n1 in m1:
8637-
if literal_hash(n1) not in m2_keys or isinstance(
8638-
get_proper_type(m1[n1]), (AnyType, UninhabitedType)
8644+
n1_hash = literal_hash(n1)
8645+
if n1_hash not in m2_exprs or (
8646+
not use_meet
8647+
and (
8648+
isinstance(get_proper_type(m1[n1]), UninhabitedType)
8649+
or isinstance(get_proper_type(m2[m2_exprs[n1_hash]]), AnyType)
8650+
)
86398651
):
86408652
result[n1] = m1[n1]
86418653
if use_meet:

mypy/checkpattern.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,15 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType:
378378
# tuples, so we instead try to narrow the entire type.
379379
# TODO: use more precise narrowing when possible (e.g. for identical shapes).
380380
new_tuple_type = TupleType(new_inner_types, current_type.partial_fallback)
381-
new_type, rest_type = self.chk.conditional_types_with_intersection(
381+
new_type, _ = self.chk.conditional_types_with_intersection(
382382
new_tuple_type, [get_type_range(current_type)], o, default=new_tuple_type
383383
)
384+
if (
385+
star_position is not None
386+
and required_patterns <= len(inner_types) - 1
387+
and all(is_uninhabited(rest) for rest in rest_inner_types)
388+
):
389+
rest_type = UninhabitedType()
384390
else:
385391
new_inner_type = UninhabitedType()
386392
for typ in new_inner_types:
@@ -460,7 +466,7 @@ def expand_starred_pattern_types(
460466
# so we only restore the type of the star item.
461467
res = []
462468
for i, t in enumerate(types):
463-
if i != star_pos:
469+
if i != star_pos or is_uninhabited(t):
464470
res.append(t)
465471
else:
466472
res.append(UnpackType(self.chk.named_generic_type("builtins.tuple", [t])))

mypy/fixer_state.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING, Final
4+
5+
if TYPE_CHECKING:
6+
from mypy.fixup import NodeFixer
7+
8+
# This is global mutable state. Don't add anything here unless there's a very
9+
# good reason. This exists as a separate file to avoid method-level import in
10+
# hot code in SymbolTableNode.node().
11+
12+
13+
class FixerState:
14+
def __init__(self) -> None:
15+
self.node_fixer: NodeFixer | None = None
16+
17+
18+
fixer_state: Final = FixerState()

0 commit comments

Comments
 (0)