Skip to content

Commit 738a1d7

Browse files
authored
Merge branch 'master' into checkmember-proto
2 parents 9c3d303 + 99e2688 commit 738a1d7

21 files changed

+285
-42
lines changed

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ repos:
1111
- id: black
1212
exclude: '^(test-data/)'
1313
- repo: https://github.com/astral-sh/ruff-pre-commit
14-
rev: v0.9.10
14+
rev: v0.11.4
1515
hooks:
1616
- id: ruff
1717
args: [--exit-non-zero-on-fix]
1818
- repo: https://github.com/python-jsonschema/check-jsonschema
19-
rev: 0.31.0
19+
rev: 0.32.1
2020
hooks:
2121
- id: check-github-workflows
2222
- id: check-github-actions
@@ -43,7 +43,7 @@ repos:
4343
# but the integration only works if shellcheck is installed
4444
- "github.com/wasilibs/go-shellcheck/cmd/[email protected]"
4545
- repo: https://github.com/woodruffw/zizmor-pre-commit
46-
rev: v1.0.1
46+
rev: v1.5.2
4747
hooks:
4848
- id: zizmor
4949
- repo: local

docs/source/command_line.rst

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -749,8 +749,19 @@ of the above sections.
749749

750750
.. option:: --strict
751751

752-
This flag mode enables all optional error checking flags. You can see the
753-
list of flags enabled by strict mode in the full :option:`mypy --help` output.
752+
This flag mode enables a defined subset of optional error-checking flags.
753+
This subset primarily includes checks for inadvertent type unsoundness (i.e
754+
strict will catch type errors as long as intentional methods like type ignore
755+
or casting were not used.)
756+
757+
Note: the :option:`--warn-unreachable` flag
758+
is not automatically enabled by the strict flag.
759+
760+
The strict flag does not take precedence over other strict-related flags.
761+
Directly specifying a flag of alternate behavior will override the
762+
behavior of strict, regardless of the order in which they are passed.
763+
You can see the list of flags enabled by strict mode in the full
764+
:option:`mypy --help` output.
754765

755766
Note: the exact list of flags enabled by running :option:`--strict` may change
756767
over time.

docs/source/runtime_troubles.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ version of Python considers legal code. This section describes these scenarios
88
and explains how to get your code running again. Generally speaking, we have
99
three tools at our disposal:
1010

11-
* Use of ``from __future__ import annotations`` (:pep:`563`)
12-
(this behaviour may eventually be made the default in a future Python version)
1311
* Use of string literal types or type comments
1412
* Use of ``typing.TYPE_CHECKING``
13+
* Use of ``from __future__ import annotations`` (:pep:`563`)
1514

1615
We provide a description of these before moving onto discussion of specific
1716
problems you may encounter.

mypy/dmypy/client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from mypy.dmypy_os import alive, kill
2121
from mypy.dmypy_util import DEFAULT_STATUS_FILE, receive, send
2222
from mypy.ipc import IPCClient, IPCException
23+
from mypy.main import RECURSION_LIMIT
2324
from mypy.util import check_python_version, get_terminal_width, should_force_color
2425
from mypy.version import __version__
2526

@@ -268,6 +269,10 @@ class BadStatus(Exception):
268269
def main(argv: list[str]) -> None:
269270
"""The code is top-down."""
270271
check_python_version("dmypy")
272+
273+
# set recursion limit consistent with mypy/main.py
274+
sys.setrecursionlimit(RECURSION_LIMIT)
275+
271276
args = parser.parse_args(argv)
272277
if not args.action:
273278
parser.print_usage()

mypy/dmypy_server.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,9 @@ def fine_grained_increment_follow_imports(
620620
t1 = time.time()
621621
manager.log(f"fine-grained increment: find_changed: {t1 - t0:.3f}s")
622622

623+
# Track all modules encountered so far. New entries for all dependencies
624+
# are added below by other module finding methods below. All dependencies
625+
# in graph but not in `seen` are considered deleted at the end of this method.
623626
seen = {source.module for source in sources}
624627

625628
# Find changed modules reachable from roots (or in roots) already in graph.
@@ -736,7 +739,9 @@ def find_reachable_changed_modules(
736739
Args:
737740
roots: modules where to start search from
738741
graph: module graph to use for the search
739-
seen: modules we've seen before that won't be visited (mutated here!!)
742+
seen: modules we've seen before that won't be visited (mutated here!!).
743+
Needed to accumulate all modules encountered during update and remove
744+
everything that no longer exists.
740745
changed_paths: which paths have changed (stop search here and return any found)
741746
742747
Return (encountered reachable changed modules,
@@ -756,7 +761,8 @@ def find_reachable_changed_modules(
756761
changed.append((nxt.module, nxt.path))
757762
elif nxt.module in graph:
758763
state = graph[nxt.module]
759-
for dep in state.dependencies:
764+
ancestors = state.ancestors or []
765+
for dep in state.dependencies + ancestors:
760766
if dep not in seen:
761767
seen.add(dep)
762768
worklist.append(BuildSource(graph[dep].path, graph[dep].id, followed=True))
@@ -775,7 +781,9 @@ def find_added_suppressed(
775781
"""Find suppressed modules that have been added (and not included in seen).
776782
777783
Args:
778-
seen: reachable modules we've seen before (mutated here!!)
784+
seen: reachable modules we've seen before (mutated here!!).
785+
Needed to accumulate all modules encountered during update and remove
786+
everything that no longer exists.
779787
780788
Return suppressed, added modules.
781789
"""

mypy/fastparse.py

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,29 @@ def parse(
239239
strip_function_bodies=strip_function_bodies,
240240
path=fnam,
241241
).visit(ast)
242+
243+
except RecursionError as e:
244+
# For very complex expressions it is possible to hit recursion limit
245+
# before reaching a leaf node.
246+
# Should reject at top level instead at bottom, since bottom would already
247+
# be at the threshold of the recursion limit, and may fail again later.
248+
# E.G. x1+x2+x3+...+xn -> BinOp(left=BinOp(left=BinOp(left=...
249+
try:
250+
# But to prove that is the cause of this particular recursion error,
251+
# try to walk the tree using builtin visitor
252+
ast3.NodeVisitor().visit(ast)
253+
except RecursionError:
254+
errors.report(
255+
-1, -1, "Source expression too complex to parse", blocker=False, code=codes.MISC
256+
)
257+
258+
tree = MypyFile([], [], False, {})
259+
260+
else:
261+
# re-raise original recursion error if it *can* be unparsed,
262+
# maybe this is some other issue that shouldn't be silenced/misdirected
263+
raise e
264+
242265
except SyntaxError as e:
243266
message = e.msg
244267
if feature_version > sys.version_info.minor and message.startswith("invalid syntax"):
@@ -381,7 +404,7 @@ def __init__(
381404
def note(self, msg: str, line: int, column: int) -> None:
382405
self.errors.report(line, column, msg, severity="note", code=codes.SYNTAX)
383406

384-
def fail(self, msg: ErrorMessage, line: int, column: int, blocker: bool = True) -> None:
407+
def fail(self, msg: ErrorMessage, line: int, column: int, blocker: bool) -> None:
385408
if blocker or not self.options.ignore_errors:
386409
# Make sure self.errors reflects any type ignores that we have parsed
387410
self.errors.set_file_ignored_lines(
@@ -406,6 +429,7 @@ def visit(self, node: AST | None) -> Any:
406429
method = "visit_" + node.__class__.__name__
407430
visitor = getattr(self, method)
408431
self.visitor_cache[typeobj] = visitor
432+
409433
return visitor(node)
410434

411435
def set_line(self, node: N, n: AstNode) -> N:
@@ -921,7 +945,12 @@ def do_func_def(
921945
):
922946
if n.returns:
923947
# PEP 484 disallows both type annotations and type comments
924-
self.fail(message_registry.DUPLICATE_TYPE_SIGNATURES, lineno, n.col_offset)
948+
self.fail(
949+
message_registry.DUPLICATE_TYPE_SIGNATURES,
950+
lineno,
951+
n.col_offset,
952+
blocker=False,
953+
)
925954
arg_types = [
926955
(
927956
a.type_annotation
@@ -933,7 +962,12 @@ def do_func_def(
933962
else:
934963
# PEP 484 disallows both type annotations and type comments
935964
if n.returns or any(a.type_annotation is not None for a in args):
936-
self.fail(message_registry.DUPLICATE_TYPE_SIGNATURES, lineno, n.col_offset)
965+
self.fail(
966+
message_registry.DUPLICATE_TYPE_SIGNATURES,
967+
lineno,
968+
n.col_offset,
969+
blocker=False,
970+
)
937971
translated_args: list[Type] = TypeConverter(
938972
self.errors, line=lineno, override_column=n.col_offset
939973
).translate_expr_list(func_type_ast.argtypes)
@@ -948,7 +982,7 @@ def do_func_def(
948982
except SyntaxError:
949983
stripped_type = n.type_comment.split("#", 2)[0].strip()
950984
err_msg = message_registry.TYPE_COMMENT_SYNTAX_ERROR_VALUE.format(stripped_type)
951-
self.fail(err_msg, lineno, n.col_offset)
985+
self.fail(err_msg, lineno, n.col_offset, blocker=False)
952986
if n.type_comment and n.type_comment[0] not in ["(", "#"]:
953987
self.note(
954988
"Suggestion: wrap argument types in parentheses", lineno, n.col_offset
@@ -970,7 +1004,12 @@ def do_func_def(
9701004
func_type = None
9711005
if any(arg_types) or return_type:
9721006
if len(arg_types) != 1 and any(isinstance(t, EllipsisType) for t in arg_types):
973-
self.fail(message_registry.ELLIPSIS_WITH_OTHER_TYPEARGS, lineno, n.col_offset)
1007+
self.fail(
1008+
message_registry.ELLIPSIS_WITH_OTHER_TYPEARGS,
1009+
lineno,
1010+
n.col_offset,
1011+
blocker=False,
1012+
)
9741013
elif len(arg_types) > len(arg_kinds):
9751014
self.fail(
9761015
message_registry.TYPE_SIGNATURE_TOO_MANY_ARGS,
@@ -1097,7 +1136,12 @@ def make_argument(
10971136
annotation = arg.annotation
10981137
type_comment = arg.type_comment
10991138
if annotation is not None and type_comment is not None:
1100-
self.fail(message_registry.DUPLICATE_TYPE_SIGNATURES, arg.lineno, arg.col_offset)
1139+
self.fail(
1140+
message_registry.DUPLICATE_TYPE_SIGNATURES,
1141+
arg.lineno,
1142+
arg.col_offset,
1143+
blocker=False,
1144+
)
11011145
arg_type = None
11021146
if annotation is not None:
11031147
arg_type = TypeConverter(self.errors, line=arg.lineno).visit(annotation)
@@ -1118,7 +1162,7 @@ def make_argument(
11181162
return argument
11191163

11201164
def fail_arg(self, msg: str, arg: ast3.arg) -> None:
1121-
self.fail(ErrorMessage(msg), arg.lineno, arg.col_offset)
1165+
self.fail(ErrorMessage(msg), arg.lineno, arg.col_offset, blocker=True)
11221166

11231167
# ClassDef(identifier name,
11241168
# expr* bases,
@@ -1164,18 +1208,21 @@ def validate_type_param(self, type_param: ast_TypeVar) -> None:
11641208
message_registry.TYPE_VAR_YIELD_EXPRESSION_IN_BOUND,
11651209
type_param.lineno,
11661210
type_param.col_offset,
1211+
blocker=True,
11671212
)
11681213
if isinstance(incorrect_expr, ast3.NamedExpr):
11691214
self.fail(
11701215
message_registry.TYPE_VAR_NAMED_EXPRESSION_IN_BOUND,
11711216
type_param.lineno,
11721217
type_param.col_offset,
1218+
blocker=True,
11731219
)
11741220
if isinstance(incorrect_expr, ast3.Await):
11751221
self.fail(
11761222
message_registry.TYPE_VAR_AWAIT_EXPRESSION_IN_BOUND,
11771223
type_param.lineno,
11781224
type_param.col_offset,
1225+
blocker=True,
11791226
)
11801227

11811228
def translate_type_params(self, type_params: list[Any]) -> list[TypeParam]:
@@ -1790,11 +1837,26 @@ def validate_type_alias(self, n: ast_TypeAlias) -> None:
17901837
if incorrect_expr is None:
17911838
return
17921839
if isinstance(incorrect_expr, (ast3.Yield, ast3.YieldFrom)):
1793-
self.fail(message_registry.TYPE_ALIAS_WITH_YIELD_EXPRESSION, n.lineno, n.col_offset)
1840+
self.fail(
1841+
message_registry.TYPE_ALIAS_WITH_YIELD_EXPRESSION,
1842+
n.lineno,
1843+
n.col_offset,
1844+
blocker=True,
1845+
)
17941846
if isinstance(incorrect_expr, ast3.NamedExpr):
1795-
self.fail(message_registry.TYPE_ALIAS_WITH_NAMED_EXPRESSION, n.lineno, n.col_offset)
1847+
self.fail(
1848+
message_registry.TYPE_ALIAS_WITH_NAMED_EXPRESSION,
1849+
n.lineno,
1850+
n.col_offset,
1851+
blocker=True,
1852+
)
17961853
if isinstance(incorrect_expr, ast3.Await):
1797-
self.fail(message_registry.TYPE_ALIAS_WITH_AWAIT_EXPRESSION, n.lineno, n.col_offset)
1854+
self.fail(
1855+
message_registry.TYPE_ALIAS_WITH_AWAIT_EXPRESSION,
1856+
n.lineno,
1857+
n.col_offset,
1858+
blocker=True,
1859+
)
17981860

17991861
# TypeAlias(identifier name, type_param* type_params, expr value)
18001862
def visit_TypeAlias(self, n: ast_TypeAlias) -> TypeAliasStmt | AssignmentStmt:

mypy/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242

4343
orig_stat: Final = os.stat
4444
MEM_PROFILE: Final = False # If True, dump memory profile
45+
RECURSION_LIMIT: Final = 2**14
4546

4647

4748
def stat_proxy(path: str) -> os.stat_result:
@@ -76,7 +77,7 @@ def main(
7677
util.check_python_version("mypy")
7778
t0 = time.time()
7879
# To log stat() calls: os.stat = stat_proxy
79-
sys.setrecursionlimit(2**14)
80+
sys.setrecursionlimit(RECURSION_LIMIT)
8081
if args is None:
8182
args = sys.argv[1:]
8283

mypy/meet.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,12 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type:
143143
]
144144
)
145145
if is_enum_overlapping_union(declared, narrowed):
146-
return original_narrowed
146+
# Quick check before reaching `is_overlapping_types`. If it's enum/literal overlap,
147+
# avoid full expansion and make it faster.
148+
assert isinstance(narrowed, UnionType)
149+
return make_simplified_union(
150+
[narrow_declared_type(declared, x) for x in narrowed.relevant_items()]
151+
)
147152
elif not is_overlapping_types(declared, narrowed, prohibit_none_typevar_overlap=True):
148153
if state.strict_optional:
149154
return UninhabitedType()

mypy/plugins/dataclasses.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,12 +359,12 @@ def transform(self) -> bool:
359359

360360
if decorator_arguments["frozen"]:
361361
if any(not parent["frozen"] for parent in parent_decorator_arguments):
362-
self._api.fail("Cannot inherit frozen dataclass from a non-frozen one", info)
362+
self._api.fail("Frozen dataclass cannot inherit from a non-frozen dataclass", info)
363363
self._propertize_callables(attributes, settable=False)
364364
self._freeze(attributes)
365365
else:
366366
if any(parent["frozen"] for parent in parent_decorator_arguments):
367-
self._api.fail("Cannot inherit non-frozen dataclass from a frozen one", info)
367+
self._api.fail("Non-frozen dataclass cannot inherit from a frozen dataclass", info)
368368
self._propertize_callables(attributes)
369369

370370
if decorator_arguments["slots"]:

mypy/semanal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5591,7 +5591,7 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None:
55915591
self.msg.unimported_type_becomes_any("Type alias target", res, s)
55925592
res = make_any_non_unimported(res)
55935593
eager = self.is_func_scope()
5594-
if isinstance(res, ProperType) and isinstance(res, Instance) and not res.args:
5594+
if isinstance(res, ProperType) and isinstance(res, Instance):
55955595
fix_instance(res, self.fail, self.note, disallow_any=False, options=self.options)
55965596
alias_node = TypeAlias(
55975597
res,

0 commit comments

Comments
 (0)