Skip to content

Commit a1dc960

Browse files
authored
Merge branch 'python:master' into fix-error-code-precedence
2 parents 4386744 + 12d53f6 commit a1dc960

Some content is hidden

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

79 files changed

+1028
-399
lines changed

.github/workflows/mypy_primer.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
persist-credentials: false
4040
- uses: actions/setup-python@v5
4141
with:
42-
python-version: "3.13"
42+
python-version: "3.14"
4343
- name: Install dependencies
4444
run: |
4545
python -m pip install -U pip

.github/workflows/test.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ jobs:
5959
toxenv: py
6060
tox_extra_args: "-n 4"
6161
test_mypyc: true
62+
- name: Test suite with py314t-ubuntu, mypyc-compiled
63+
python: '3.14t'
64+
os: ubuntu-24.04-arm
65+
toxenv: py
66+
tox_extra_args: "-n 4"
67+
test_mypyc: true
6268
- name: Test suite with py314-windows-64
6369
python: '3.14'
6470
os: windows-latest

docs/source/additional_features.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ Type annotations can be added as follows:
135135

136136
.. code-block:: python
137137
138-
import attr
138+
import attrs
139139
140140
@attrs.define
141141
class A:

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>=0.9.0
66
tomli>=1.1.0; python_version<'3.11'
7-
librt>=0.6.2
7+
librt>=0.6.2; platform_python_implementation != 'PyPy'

mypy/build.py

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,14 @@
4343
from librt.internal import cache_version
4444

4545
import mypy.semanal_main
46-
from mypy.cache import CACHE_VERSION, CacheMeta, ReadBuffer, WriteBuffer, write_json
46+
from mypy.cache import (
47+
CACHE_VERSION,
48+
CacheMeta,
49+
ReadBuffer,
50+
SerializedError,
51+
WriteBuffer,
52+
write_json,
53+
)
4754
from mypy.checker import TypeChecker
4855
from mypy.defaults import (
4956
WORKER_CONNECTION_TIMEOUT,
@@ -52,7 +59,7 @@
5259
WORKER_START_TIMEOUT,
5360
)
5461
from mypy.error_formatter import OUTPUT_CHOICES, ErrorFormatter
55-
from mypy.errors import CompileError, ErrorInfo, Errors, report_internal_error
62+
from mypy.errors import CompileError, ErrorInfo, Errors, ErrorTuple, report_internal_error
5663
from mypy.graph_utils import prepare_sccs, strongly_connected_components, topsort
5764
from mypy.indirection import TypeIndirectionVisitor
5865
from mypy.ipc import BadStatus, IPCClient, read_status, ready_to_read, receive, send
@@ -2046,7 +2053,7 @@ class State:
20462053
dep_hashes: dict[str, bytes] = {}
20472054

20482055
# List of errors reported for this file last time.
2049-
error_lines: list[str] = []
2056+
error_lines: list[SerializedError] = []
20502057

20512058
# Parent package, its parent, etc.
20522059
ancestors: list[str] | None = None
@@ -3511,9 +3518,13 @@ def find_stale_sccs(
35113518
scc = order_ascc_ex(graph, ascc)
35123519
for id in scc:
35133520
if graph[id].error_lines:
3514-
manager.flush_errors(
3515-
manager.errors.simplify_path(graph[id].xpath), graph[id].error_lines, False
3521+
path = manager.errors.simplify_path(graph[id].xpath)
3522+
formatted = manager.errors.format_messages(
3523+
path,
3524+
deserialize_codes(graph[id].error_lines),
3525+
formatter=manager.error_formatter,
35163526
)
3527+
manager.flush_errors(path, formatted, False)
35173528
fresh_sccs.append(ascc)
35183529
else:
35193530
size = len(ascc.mod_ids)
@@ -3759,21 +3770,24 @@ def process_stale_scc(
37593770
# Flush errors, and write cache in two phases: first data files, then meta files.
37603771
meta_tuples = {}
37613772
errors_by_id = {}
3773+
formatted_by_id = {}
37623774
for id in stale:
37633775
if graph[id].xpath not in manager.errors.ignored_files:
3764-
errors = manager.errors.file_messages(
3765-
graph[id].xpath, formatter=manager.error_formatter
3776+
errors = manager.errors.file_messages(graph[id].xpath)
3777+
formatted = manager.errors.format_messages(
3778+
graph[id].xpath, errors, formatter=manager.error_formatter
37663779
)
3767-
manager.flush_errors(manager.errors.simplify_path(graph[id].xpath), errors, False)
3780+
manager.flush_errors(manager.errors.simplify_path(graph[id].xpath), formatted, False)
37683781
errors_by_id[id] = errors
3782+
formatted_by_id[id] = formatted
37693783
meta_tuples[id] = graph[id].write_cache()
37703784
for id in stale:
37713785
meta_tuple = meta_tuples[id]
37723786
if meta_tuple is None:
37733787
continue
37743788
meta, meta_file = meta_tuple
37753789
meta.dep_hashes = [graph[dep].interface_hash for dep in graph[id].dependencies]
3776-
meta.error_lines = errors_by_id.get(id, [])
3790+
meta.error_lines = serialize_codes(errors_by_id.get(id, []))
37773791
write_cache_meta(meta, manager, meta_file)
37783792
manager.done_sccs.add(ascc.id)
37793793
manager.add_stats(
@@ -3785,7 +3799,7 @@ def process_stale_scc(
37853799
)
37863800
scc_result = {}
37873801
for id in scc:
3788-
scc_result[id] = graph[id].interface_hash.hex(), errors_by_id.get(id, [])
3802+
scc_result[id] = graph[id].interface_hash.hex(), formatted_by_id.get(id, [])
37893803
return scc_result
37903804

37913805

@@ -3932,3 +3946,26 @@ def sccs_to_bytes(sccs: list[SCC]) -> bytes:
39323946
buf = WriteBuffer()
39333947
write_json(buf, {"sccs": scc_tuples})
39343948
return buf.getvalue()
3949+
3950+
3951+
def serialize_codes(errs: list[ErrorTuple]) -> list[SerializedError]:
3952+
return [
3953+
(path, line, column, end_line, end_column, severity, message, code.code if code else None)
3954+
for path, line, column, end_line, end_column, severity, message, code in errs
3955+
]
3956+
3957+
3958+
def deserialize_codes(errs: list[SerializedError]) -> list[ErrorTuple]:
3959+
return [
3960+
(
3961+
path,
3962+
line,
3963+
column,
3964+
end_line,
3965+
end_column,
3966+
severity,
3967+
message,
3968+
codes.error_codes.get(code) if code else None,
3969+
)
3970+
for path, line, column, end_line, end_column, severity, message, code in errs
3971+
]

mypy/cache.py

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@
6969
from mypy_extensions import u8
7070

7171
# High-level cache layout format
72-
CACHE_VERSION: Final = 0
72+
CACHE_VERSION: Final = 1
73+
74+
SerializedError: _TypeAlias = tuple[str | None, int, int, int, int, str, str, str | None]
7375

7476

7577
class CacheMeta:
@@ -92,7 +94,7 @@ def __init__(
9294
dep_lines: list[int],
9395
dep_hashes: list[bytes],
9496
interface_hash: bytes,
95-
error_lines: list[str],
97+
error_lines: list[SerializedError],
9698
version_id: str,
9799
ignore_all: bool,
98100
plugin_data: Any,
@@ -157,7 +159,7 @@ def deserialize(cls, meta: dict[str, Any], data_file: str) -> CacheMeta | None:
157159
dep_lines=meta["dep_lines"],
158160
dep_hashes=[bytes.fromhex(dep) for dep in meta["dep_hashes"]],
159161
interface_hash=bytes.fromhex(meta["interface_hash"]),
160-
error_lines=meta["error_lines"],
162+
error_lines=[tuple(err) for err in meta["error_lines"]],
161163
version_id=meta["version_id"],
162164
ignore_all=meta["ignore_all"],
163165
plugin_data=meta["plugin_data"],
@@ -179,7 +181,7 @@ def write(self, data: WriteBuffer) -> None:
179181
write_int_list(data, self.dep_lines)
180182
write_bytes_list(data, self.dep_hashes)
181183
write_bytes(data, self.interface_hash)
182-
write_str_list(data, self.error_lines)
184+
write_errors(data, self.error_lines)
183185
write_str(data, self.version_id)
184186
write_bool(data, self.ignore_all)
185187
# Plugin data may be not a dictionary, so we use
@@ -204,7 +206,7 @@ def read(cls, data: ReadBuffer, data_file: str) -> CacheMeta | None:
204206
dep_lines=read_int_list(data),
205207
dep_hashes=read_bytes_list(data),
206208
interface_hash=read_bytes(data),
207-
error_lines=read_str_list(data),
209+
error_lines=read_errors(data),
208210
version_id=read_str(data),
209211
ignore_all=read_bool(data),
210212
plugin_data=read_json_value(data),
@@ -231,6 +233,7 @@ def read(cls, data: ReadBuffer, data_file: str) -> CacheMeta | None:
231233
LIST_INT: Final[Tag] = 21
232234
LIST_STR: Final[Tag] = 22
233235
LIST_BYTES: Final[Tag] = 23
236+
TUPLE_GEN: Final[Tag] = 24
234237
DICT_STR_GEN: Final[Tag] = 30
235238

236239
# Misc classes.
@@ -391,12 +394,11 @@ def write_str_opt_list(data: WriteBuffer, value: list[str | None]) -> None:
391394

392395

393396
Value: _TypeAlias = None | int | str | bool
394-
JsonValue: _TypeAlias = Value | list["JsonValue"] | dict[str, "JsonValue"]
395397

396-
# Currently tuples are used by mypyc plugin. They will be normalized to
397-
# JSON lists after a roundtrip.
398-
JsonValueEx: _TypeAlias = (
399-
Value | list["JsonValueEx"] | dict[str, "JsonValueEx"] | tuple["JsonValueEx", ...]
398+
# Our JSON format is somewhat non-standard as we distinguish lists and tuples.
399+
# This is convenient for some internal things, like mypyc plugin and error serialization.
400+
JsonValue: _TypeAlias = (
401+
Value | list["JsonValue"] | dict[str, "JsonValue"] | tuple["JsonValue", ...]
400402
)
401403

402404

@@ -415,13 +417,16 @@ def read_json_value(data: ReadBuffer) -> JsonValue:
415417
if tag == LIST_GEN:
416418
size = read_int_bare(data)
417419
return [read_json_value(data) for _ in range(size)]
420+
if tag == TUPLE_GEN:
421+
size = read_int_bare(data)
422+
return tuple(read_json_value(data) for _ in range(size))
418423
if tag == DICT_STR_GEN:
419424
size = read_int_bare(data)
420425
return {read_str_bare(data): read_json_value(data) for _ in range(size)}
421426
assert False, f"Invalid JSON tag: {tag}"
422427

423428

424-
def write_json_value(data: WriteBuffer, value: JsonValueEx) -> None:
429+
def write_json_value(data: WriteBuffer, value: JsonValue) -> None:
425430
if value is None:
426431
write_tag(data, LITERAL_NONE)
427432
elif isinstance(value, bool):
@@ -432,11 +437,16 @@ def write_json_value(data: WriteBuffer, value: JsonValueEx) -> None:
432437
elif isinstance(value, str):
433438
write_tag(data, LITERAL_STR)
434439
write_str_bare(data, value)
435-
elif isinstance(value, (list, tuple)):
440+
elif isinstance(value, list):
436441
write_tag(data, LIST_GEN)
437442
write_int_bare(data, len(value))
438443
for val in value:
439444
write_json_value(data, val)
445+
elif isinstance(value, tuple):
446+
write_tag(data, TUPLE_GEN)
447+
write_int_bare(data, len(value))
448+
for val in value:
449+
write_json_value(data, val)
440450
elif isinstance(value, dict):
441451
write_tag(data, DICT_STR_GEN)
442452
write_int_bare(data, len(value))
@@ -461,3 +471,38 @@ def write_json(data: WriteBuffer, value: dict[str, Any]) -> None:
461471
for key in sorted(value):
462472
write_str_bare(data, key)
463473
write_json_value(data, value[key])
474+
475+
476+
def write_errors(data: WriteBuffer, errs: list[SerializedError]) -> None:
477+
write_tag(data, LIST_GEN)
478+
write_int_bare(data, len(errs))
479+
for path, line, column, end_line, end_column, severity, message, code in errs:
480+
write_tag(data, TUPLE_GEN)
481+
write_str_opt(data, path)
482+
write_int(data, line)
483+
write_int(data, column)
484+
write_int(data, end_line)
485+
write_int(data, end_column)
486+
write_str(data, severity)
487+
write_str(data, message)
488+
write_str_opt(data, code)
489+
490+
491+
def read_errors(data: ReadBuffer) -> list[SerializedError]:
492+
assert read_tag(data) == LIST_GEN
493+
result = []
494+
for _ in range(read_int_bare(data)):
495+
assert read_tag(data) == TUPLE_GEN
496+
result.append(
497+
(
498+
read_str_opt(data),
499+
read_int(data),
500+
read_int(data),
501+
read_int(data),
502+
read_int(data),
503+
read_str(data),
504+
read_str(data),
505+
read_str_opt(data),
506+
)
507+
)
508+
return result

mypy/checker.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4116,8 +4116,19 @@ def check_multi_assignment(
41164116
lvalues, rvalue, rvalue_type, context, infer_lvalue_type
41174117
)
41184118
else:
4119+
rvalue_literal = rvalue_type
41194120
if isinstance(rvalue_type, Instance) and rvalue_type.type.fullname == "builtins.str":
4121+
if rvalue_type.last_known_value is None:
4122+
self.msg.unpacking_strings_disallowed(context)
4123+
else:
4124+
rvalue_literal = rvalue_type.last_known_value
4125+
if (
4126+
isinstance(rvalue_literal, LiteralType)
4127+
and isinstance(rvalue_literal.value, str)
4128+
and len(lvalues) != len(rvalue_literal.value)
4129+
):
41204130
self.msg.unpacking_strings_disallowed(context)
4131+
41214132
self.check_multi_assignment_from_iterable(
41224133
lvalues, rvalue_type, context, infer_lvalue_type
41234134
)
@@ -4363,9 +4374,7 @@ def check_multi_assignment_from_iterable(
43634374
infer_lvalue_type: bool = True,
43644375
) -> None:
43654376
rvalue_type = get_proper_type(rvalue_type)
4366-
if self.type_is_iterable(rvalue_type) and isinstance(
4367-
rvalue_type, (Instance, CallableType, TypeType, Overloaded)
4368-
):
4377+
if self.type_is_iterable(rvalue_type):
43694378
item_type = self.iterable_item_type(rvalue_type, context)
43704379
for lv in lvalues:
43714380
if isinstance(lv, StarExpr):
@@ -7803,9 +7812,7 @@ def note(
78037812
return
78047813
self.msg.note(msg, context, offset=offset, code=code)
78057814

7806-
def iterable_item_type(
7807-
self, it: Instance | CallableType | TypeType | Overloaded, context: Context
7808-
) -> Type:
7815+
def iterable_item_type(self, it: ProperType, context: Context) -> Type:
78097816
if isinstance(it, Instance):
78107817
iterable = map_instance_to_supertype(it, self.lookup_typeinfo("typing.Iterable"))
78117818
item_type = iterable.args[0]
@@ -8744,8 +8751,9 @@ def is_unsafe_overlapping_overload_signatures(
87448751
# Note: We repeat this check twice in both directions compensate for slight
87458752
# asymmetries in 'is_callable_compatible'.
87468753

8754+
other_expanded = expand_callable_variants(other)
87478755
for sig_variant in expand_callable_variants(signature):
8748-
for other_variant in expand_callable_variants(other):
8756+
for other_variant in other_expanded:
87498757
# Using only expanded callables may cause false negatives, we can add
87508758
# more variants (e.g. using inference between callables) in the future.
87518759
if is_subset_no_promote(sig_variant.ret_type, other_variant.ret_type):

mypy/checkpattern.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
Type,
4747
TypedDictType,
4848
TypeOfAny,
49+
TypeType,
4950
TypeVarTupleType,
5051
TypeVarType,
5152
UninhabitedType,
@@ -556,6 +557,8 @@ def visit_class_pattern(self, o: ClassPattern) -> PatternType:
556557
fallback = self.chk.named_type("builtins.function")
557558
any_type = AnyType(TypeOfAny.unannotated)
558559
typ = callable_with_ellipsis(any_type, ret_type=any_type, fallback=fallback)
560+
elif isinstance(p_typ, TypeType) and isinstance(p_typ.item, NoneType):
561+
typ = p_typ.item
559562
elif not isinstance(p_typ, AnyType):
560563
self.msg.fail(
561564
message_registry.CLASS_PATTERN_TYPE_REQUIRED.format(

0 commit comments

Comments
 (0)