Skip to content

Commit f258639

Browse files
bcallerBen Caller
authored andcommitted
Don't crash on pathological case of f(g(a)(b)(c))
It's rare, but a curried function call can appear within a function call. It was raising a RuntimeError in VarsVisitor. We don't build a cfg properly for curried functions which is fine for now, but we don't need to crash. At least there is now defined behaviour.
1 parent 5b50922 commit f258639

File tree

2 files changed

+29
-2
lines changed

2 files changed

+29
-2
lines changed

pyt/helper_visitors/vars_visitor.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,32 @@ def visit_Call(self, node):
9696
# func.value.id is html
9797
# We want replace
9898
self.result.append('ret_' + arg.func.attr)
99+
elif isinstance(arg.func, ast.Call):
100+
self.visit_curried_call_inside_call_args(arg)
99101
else:
100-
# Deal with it when we have code that triggers it.
101-
raise
102+
raise Exception('Cannot visit vars of ' + ast.dump(arg))
102103
else:
103104
self.visit(arg)
104105

106+
def visit_curried_call_inside_call_args(self, inner_call):
107+
# Curried functions aren't supported really, but we now at least have a defined behaviour.
108+
# In f(g(a)(b)(c)), inner_call is the Call node with argument c
109+
# Try to get the name of curried function g
110+
curried_func = inner_call.func.func
111+
while isinstance(curried_func, ast.Call):
112+
curried_func = curried_func.func
113+
if isinstance(curried_func, ast.Name):
114+
self.result.append('ret_' + curried_func.id)
115+
elif isinstance(curried_func, ast.Attribute):
116+
self.result.append('ret_' + curried_func.attr)
117+
118+
# Visit all arguments except a (ignore the curried function g)
119+
not_curried = inner_call
120+
while not_curried.func is not curried_func:
121+
for arg in itertools.chain(not_curried.args, not_curried.keywords):
122+
self.visit(arg.value if isinstance(arg, ast.keyword) else arg)
123+
not_curried = not_curried.func
124+
105125
def visit_Attribute(self, node):
106126
if not isinstance(node.value, ast.Name):
107127
self.visit(node.value)

tests/helper_visitors/vars_visitor_test.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ def test_call7(self):
5252
vars = self.perform_vars_on_expression("resp = make_response(html.replace.bar('{{ param }}', param))")
5353
self.assertEqual(vars.result, ['resp', 'ret_bar'])
5454

55+
def test_curried_function(self):
56+
# Curried functions aren't supported really, but we now at least have a defined behaviour.
57+
vars = self.perform_vars_on_expression('f(g.h(a)(b))')
58+
self.assertCountEqual(vars.result, ['ret_h', 'b'])
59+
vars = self.perform_vars_on_expression('f(g(a)(b)(c)(d, e=f))')
60+
self.assertCountEqual(vars.result, ['ret_g', 'b', 'c', 'd', 'f'])
61+
5562
def test_keyword_vararg(self):
5663
vars = self.perform_vars_on_expression('print(arg = x)')
5764
self.assertEqual(vars.result, ['x'])

0 commit comments

Comments
 (0)