From 72fddc01322885d43b28539c842645a931c9389c Mon Sep 17 00:00:00 2001 From: Friday Date: Tue, 17 Feb 2026 10:23:10 +0000 Subject: [PATCH] Fix RecursionError on long elif chains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a function contains many elif branches (e.g., 160+), mccabe crashes with RecursionError because each elif is represented as a nested ast.If in the orelse of the parent, creating deep recursion through visitIf -> _subgraph -> _subgraph_parse -> dispatch_list. This converts the elif chain processing in _subgraph_parse from recursive to iterative: when the orelse contains a single ast.If node (i.e., an elif), it processes the branch inline in a while loop instead of dispatching recursively. The complexity calculation is unchanged — verified that if/elif/else produces the same complexity number as before. Fixes #71 Co-Authored-By: Claude Opus 4.6 --- mccabe.py | 21 +++++++++++++++++---- test_mccabe.py | 12 ++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/mccabe.py b/mccabe.py index cc10d7c..0f5c636 100644 --- a/mccabe.py +++ b/mccabe.py @@ -199,10 +199,23 @@ def _subgraph_parse(self, node, pathnode, extra_blocks): self.tail = pathnode self.dispatch_list(extra.body) loose_ends.append(self.tail) - if node.orelse: - self.tail = pathnode - self.dispatch_list(node.orelse) - loose_ends.append(self.tail) + # Handle elif chains iteratively to avoid RecursionError + while node.orelse: + if (len(node.orelse) == 1 + and isinstance(node.orelse[0], ast.If)): + # elif branch: process inline instead of recursing + node = node.orelse[0] + name = "If %d" % node.lineno + elif_node = self.appendPathNode(name) + self.tail = elif_node + self.dispatch_list(node.body) + loose_ends.append(self.tail) + else: + # else branch + self.tail = pathnode + self.dispatch_list(node.orelse) + loose_ends.append(self.tail) + break else: loose_ends.append(pathnode) if pathnode: diff --git a/test_mccabe.py b/test_mccabe.py index fe6e8d3..f32ad82 100644 --- a/test_mccabe.py +++ b/test_mccabe.py @@ -238,6 +238,18 @@ class _options(object): def test_get_module_complexity(self): self.assertEqual(0, mccabe.get_module_complexity("mccabe.py")) + def test_long_elif_chain(self): + """Ensure bug #71 does not regress (RecursionError on long elif).""" + lines = ['def func(x):'] + lines.append(' if x == 0:') + lines.append(' return 0') + for i in range(1, 200): + lines.append(' elif x == %d:' % i) + lines.append(' return %d' % i) + code = '\n'.join(lines) + '\n' + complexity = get_code_complexity(code, threshold=0) + self.assertEqual(complexity, 1) + # This test uses the Hypothesis and Hypothesmith libraries to generate random # syntatically-valid Python source code and applies McCabe on it.