Skip to content

Commit df60055

Browse files
authored
Allow deeper recursion in mypy daemon, better error reporting (python#17707)
Fixes python#17706 Handles recursion error during parse of too complex expressions, differentiates from internal recursion errors by attempting to unparse the ast. If builtin ast.unparse fails, then the error is ignored. Otherwise, re-raises.
1 parent a3ce6d5 commit df60055

File tree

3 files changed

+31
-1
lines changed

3 files changed

+31
-1
lines changed

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/fastparse.py

Lines changed: 24 additions & 0 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"):
@@ -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:

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

0 commit comments

Comments
 (0)