Skip to content

Commit 7fab9d9

Browse files
authored
Merge branch 'master' into strict-arg
2 parents 208dddf + b9056f9 commit 7fab9d9

23 files changed

+589
-537
lines changed

.github/workflows/mypy_primer.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ jobs:
7474
name: Save PR number
7575
run: |
7676
echo ${{ github.event.pull_request.number }} | tee pr_number.txt
77-
- if: ${{ matrix.shard-index == 0 }}
78-
name: Upload mypy_primer diff + PR number
77+
- name: Upload mypy_primer diff + PR number
7978
uses: actions/upload-artifact@v4
79+
if: ${{ matrix.shard-index == 0 }}
8080
with:
8181
name: mypy_primer_diffs-${{ matrix.shard-index }}
8282
path: |

.github/workflows/mypy_primer_comment.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ jobs:
4848
with:
4949
github-token: ${{ secrets.GITHUB_TOKEN }}
5050
script: |
51-
const MAX_CHARACTERS = 30000
51+
const MAX_CHARACTERS = 50000
5252
const MAX_CHARACTERS_PER_PROJECT = MAX_CHARACTERS / 3
5353
5454
const fs = require('fs')

CHANGELOG.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,25 @@
22

33
## Next release
44

5-
...
5+
### `--strict-bytes`
6+
7+
By default, mypy treats an annotation of ``bytes`` as permitting ``bytearray`` and ``memoryview``.
8+
[PEP 688](https://peps.python.org/pep-0688) specified the removal of this special case.
9+
Use this flag to disable this behavior. `--strict-bytes` will be enabled by default in **mypy 2.0**.
10+
11+
Contributed by Ali Hamdan (PR [18137](https://github.com/python/mypy/pull/18263/)) and
12+
Shantanu Jain (PR [13952](https://github.com/python/mypy/pull/13952)).
13+
14+
### Improvements to partial type handling in loops
15+
16+
This change results in mypy better modelling control flow within loops and hence detecting several
17+
issues it previously did not detect. In some cases, this change may require use of an additional
18+
explicit annotation of a variable.
19+
20+
Contributed by Christoph Tyralla (PR [18180](https://github.com/python/mypy/pull/18180)).
21+
22+
(Speaking of partial types, another reminder that mypy plans on enabling `--local-partial-types`
23+
by default in **mypy 2.0**).
624

725
## Mypy 1.14
826

mypy/main.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from collections import defaultdict
1111
from gettext import gettext
1212
from io import TextIOWrapper
13-
from typing import IO, Any, Final, NoReturn, Sequence, TextIO
13+
from typing import IO, Any, Final, NoReturn, Protocol, Sequence, TextIO
1414

1515
from mypy import build, defaults, state, util
1616
from mypy.config_parser import (
@@ -35,6 +35,11 @@
3535
from mypy.split_namespace import SplitNamespace
3636
from mypy.version import __version__
3737

38+
39+
class _SupportsWrite(Protocol):
40+
def write(self, s: str, /) -> object: ...
41+
42+
3843
orig_stat: Final = os.stat
3944
MEM_PROFILE: Final = False # If True, dump memory profile
4045

@@ -372,17 +377,17 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
372377
# =====================
373378
# Help-printing methods
374379
# =====================
375-
def print_usage(self, file: IO[str] | None = None) -> None:
380+
def print_usage(self, file: _SupportsWrite | None = None) -> None:
376381
if file is None:
377382
file = self.stdout
378383
self._print_message(self.format_usage(), file)
379384

380-
def print_help(self, file: IO[str] | None = None) -> None:
385+
def print_help(self, file: _SupportsWrite | None = None) -> None:
381386
if file is None:
382387
file = self.stdout
383388
self._print_message(self.format_help(), file)
384389

385-
def _print_message(self, message: str, file: IO[str] | None = None) -> None:
390+
def _print_message(self, message: str, file: _SupportsWrite | None = None) -> None:
386391
if message:
387392
if file is None:
388393
file = self.stderr

mypy/messages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2700,7 +2700,7 @@ def format_literal_value(typ: LiteralType) -> str:
27002700
if func.is_type_obj():
27012701
# The type of a type object type can be derived from the
27022702
# return type (this always works).
2703-
return format(TypeType.make_normalized(erase_type(func.items[0].ret_type)))
2703+
return format(TypeType.make_normalized(func.items[0].ret_type))
27042704
elif isinstance(func, CallableType):
27052705
if func.type_guard is not None:
27062706
return_type = f"TypeGuard[{format(func.type_guard)}]"

mypy/mixedtraverser.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ def __init__(self) -> None:
3030

3131
# Symbol nodes
3232

33-
def visit_var(self, var: Var) -> None:
33+
def visit_var(self, var: Var, /) -> None:
3434
self.visit_optional_type(var.type)
3535

36-
def visit_func(self, o: FuncItem) -> None:
36+
def visit_func(self, o: FuncItem, /) -> None:
3737
super().visit_func(o)
3838
self.visit_optional_type(o.type)
3939

40-
def visit_class_def(self, o: ClassDef) -> None:
40+
def visit_class_def(self, o: ClassDef, /) -> None:
4141
# TODO: Should we visit generated methods/variables as well, either here or in
4242
# TraverserVisitor?
4343
super().visit_class_def(o)
@@ -46,67 +46,67 @@ def visit_class_def(self, o: ClassDef) -> None:
4646
for base in info.bases:
4747
base.accept(self)
4848

49-
def visit_type_alias_expr(self, o: TypeAliasExpr) -> None:
49+
def visit_type_alias_expr(self, o: TypeAliasExpr, /) -> None:
5050
super().visit_type_alias_expr(o)
5151
self.in_type_alias_expr = True
5252
o.node.target.accept(self)
5353
self.in_type_alias_expr = False
5454

55-
def visit_type_var_expr(self, o: TypeVarExpr) -> None:
55+
def visit_type_var_expr(self, o: TypeVarExpr, /) -> None:
5656
super().visit_type_var_expr(o)
5757
o.upper_bound.accept(self)
5858
for value in o.values:
5959
value.accept(self)
6060

61-
def visit_typeddict_expr(self, o: TypedDictExpr) -> None:
61+
def visit_typeddict_expr(self, o: TypedDictExpr, /) -> None:
6262
super().visit_typeddict_expr(o)
6363
self.visit_optional_type(o.info.typeddict_type)
6464

65-
def visit_namedtuple_expr(self, o: NamedTupleExpr) -> None:
65+
def visit_namedtuple_expr(self, o: NamedTupleExpr, /) -> None:
6666
super().visit_namedtuple_expr(o)
6767
assert o.info.tuple_type
6868
o.info.tuple_type.accept(self)
6969

70-
def visit__promote_expr(self, o: PromoteExpr) -> None:
70+
def visit__promote_expr(self, o: PromoteExpr, /) -> None:
7171
super().visit__promote_expr(o)
7272
o.type.accept(self)
7373

74-
def visit_newtype_expr(self, o: NewTypeExpr) -> None:
74+
def visit_newtype_expr(self, o: NewTypeExpr, /) -> None:
7575
super().visit_newtype_expr(o)
7676
self.visit_optional_type(o.old_type)
7777

7878
# Statements
7979

80-
def visit_assignment_stmt(self, o: AssignmentStmt) -> None:
80+
def visit_assignment_stmt(self, o: AssignmentStmt, /) -> None:
8181
super().visit_assignment_stmt(o)
8282
self.visit_optional_type(o.type)
8383

84-
def visit_for_stmt(self, o: ForStmt) -> None:
84+
def visit_for_stmt(self, o: ForStmt, /) -> None:
8585
super().visit_for_stmt(o)
8686
self.visit_optional_type(o.index_type)
8787

88-
def visit_with_stmt(self, o: WithStmt) -> None:
88+
def visit_with_stmt(self, o: WithStmt, /) -> None:
8989
super().visit_with_stmt(o)
9090
for typ in o.analyzed_types:
9191
typ.accept(self)
9292

9393
# Expressions
9494

95-
def visit_cast_expr(self, o: CastExpr) -> None:
95+
def visit_cast_expr(self, o: CastExpr, /) -> None:
9696
super().visit_cast_expr(o)
9797
o.type.accept(self)
9898

99-
def visit_assert_type_expr(self, o: AssertTypeExpr) -> None:
99+
def visit_assert_type_expr(self, o: AssertTypeExpr, /) -> None:
100100
super().visit_assert_type_expr(o)
101101
o.type.accept(self)
102102

103-
def visit_type_application(self, o: TypeApplication) -> None:
103+
def visit_type_application(self, o: TypeApplication, /) -> None:
104104
super().visit_type_application(o)
105105
for t in o.types:
106106
t.accept(self)
107107

108108
# Helpers
109109

110-
def visit_optional_type(self, t: Type | None) -> None:
110+
def visit_optional_type(self, t: Type | None, /) -> None:
111111
if t:
112112
t.accept(self)

mypy/stubtest.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ def _verify_arg_default_value(
670670
stub_arg: nodes.Argument, runtime_arg: inspect.Parameter
671671
) -> Iterator[str]:
672672
"""Checks whether argument default values are compatible."""
673-
if runtime_arg.default != inspect.Parameter.empty:
673+
if runtime_arg.default is not inspect.Parameter.empty:
674674
if stub_arg.kind.is_required():
675675
yield (
676676
f'runtime argument "{runtime_arg.name}" '
@@ -705,18 +705,26 @@ def _verify_arg_default_value(
705705
stub_default is not UNKNOWN
706706
and stub_default is not ...
707707
and runtime_arg.default is not UNREPRESENTABLE
708-
and (
709-
stub_default != runtime_arg.default
710-
# We want the types to match exactly, e.g. in case the stub has
711-
# True and the runtime has 1 (or vice versa).
712-
or type(stub_default) is not type(runtime_arg.default)
713-
)
714708
):
715-
yield (
716-
f'runtime argument "{runtime_arg.name}" '
717-
f"has a default value of {runtime_arg.default!r}, "
718-
f"which is different from stub argument default {stub_default!r}"
719-
)
709+
defaults_match = True
710+
# We want the types to match exactly, e.g. in case the stub has
711+
# True and the runtime has 1 (or vice versa).
712+
if type(stub_default) is not type(runtime_arg.default):
713+
defaults_match = False
714+
else:
715+
try:
716+
defaults_match = bool(stub_default == runtime_arg.default)
717+
except Exception:
718+
# Exception can be raised in bool dunder method (e.g. numpy arrays)
719+
# At this point, consider the default to be different, it is probably
720+
# too complex to put in a stub anyway.
721+
defaults_match = False
722+
if not defaults_match:
723+
yield (
724+
f'runtime argument "{runtime_arg.name}" '
725+
f"has a default value of {runtime_arg.default!r}, "
726+
f"which is different from stub argument default {stub_default!r}"
727+
)
720728
else:
721729
if stub_arg.kind.is_optional():
722730
yield (
@@ -758,7 +766,7 @@ def get_type(arg: Any) -> str | None:
758766

759767
def has_default(arg: Any) -> bool:
760768
if isinstance(arg, inspect.Parameter):
761-
return bool(arg.default != inspect.Parameter.empty)
769+
return arg.default is not inspect.Parameter.empty
762770
if isinstance(arg, nodes.Argument):
763771
return arg.kind.is_optional()
764772
raise AssertionError
@@ -1628,13 +1636,13 @@ def anytype() -> mypy.types.AnyType:
16281636
arg_names.append(
16291637
None if arg.kind == inspect.Parameter.POSITIONAL_ONLY else arg.name
16301638
)
1631-
has_default = arg.default == inspect.Parameter.empty
1639+
no_default = arg.default is inspect.Parameter.empty
16321640
if arg.kind == inspect.Parameter.POSITIONAL_ONLY:
1633-
arg_kinds.append(nodes.ARG_POS if has_default else nodes.ARG_OPT)
1641+
arg_kinds.append(nodes.ARG_POS if no_default else nodes.ARG_OPT)
16341642
elif arg.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
1635-
arg_kinds.append(nodes.ARG_POS if has_default else nodes.ARG_OPT)
1643+
arg_kinds.append(nodes.ARG_POS if no_default else nodes.ARG_OPT)
16361644
elif arg.kind == inspect.Parameter.KEYWORD_ONLY:
1637-
arg_kinds.append(nodes.ARG_NAMED if has_default else nodes.ARG_NAMED_OPT)
1645+
arg_kinds.append(nodes.ARG_NAMED if no_default else nodes.ARG_NAMED_OPT)
16381646
elif arg.kind == inspect.Parameter.VAR_POSITIONAL:
16391647
arg_kinds.append(nodes.ARG_STAR)
16401648
elif arg.kind == inspect.Parameter.VAR_KEYWORD:

mypy/test/test_find_sources.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,20 @@ class FakeFSCache(FileSystemCache):
1717
def __init__(self, files: set[str]) -> None:
1818
self.files = {os.path.abspath(f) for f in files}
1919

20-
def isfile(self, file: str) -> bool:
21-
return file in self.files
20+
def isfile(self, path: str) -> bool:
21+
return path in self.files
2222

23-
def isdir(self, dir: str) -> bool:
24-
if not dir.endswith(os.sep):
25-
dir += os.sep
26-
return any(f.startswith(dir) for f in self.files)
23+
def isdir(self, path: str) -> bool:
24+
if not path.endswith(os.sep):
25+
path += os.sep
26+
return any(f.startswith(path) for f in self.files)
2727

28-
def listdir(self, dir: str) -> list[str]:
29-
if not dir.endswith(os.sep):
30-
dir += os.sep
31-
return list({f[len(dir) :].split(os.sep)[0] for f in self.files if f.startswith(dir)})
28+
def listdir(self, path: str) -> list[str]:
29+
if not path.endswith(os.sep):
30+
path += os.sep
31+
return list({f[len(path) :].split(os.sep)[0] for f in self.files if f.startswith(path)})
3232

33-
def init_under_package_root(self, file: str) -> bool:
33+
def init_under_package_root(self, path: str) -> bool:
3434
return False
3535

3636

mypy/test/testpep561.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ class PEP561Suite(DataSuite):
2323
files = ["pep561.test"]
2424
base_path = "."
2525

26-
def run_case(self, test_case: DataDrivenTestCase) -> None:
27-
test_pep561(test_case)
26+
def run_case(self, testcase: DataDrivenTestCase) -> None:
27+
test_pep561(testcase)
2828

2929

3030
@contextmanager

mypy/test/teststubtest.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,18 @@ def f11(text=None) -> None: pass
529529
error="f11",
530530
)
531531

532+
# Simulate numpy ndarray.__bool__ that raises an error
533+
yield Case(
534+
stub="def f12(x=1): ...",
535+
runtime="""
536+
class _ndarray:
537+
def __eq__(self, obj): return self
538+
def __bool__(self): raise ValueError
539+
def f12(x=_ndarray()) -> None: pass
540+
""",
541+
error="f12",
542+
)
543+
532544
@collect_cases
533545
def test_static_class_method(self) -> Iterator[Case]:
534546
yield Case(

0 commit comments

Comments
 (0)