Skip to content

Commit

Permalink
Fixed recomputation of calls in generator expr
Browse files Browse the repository at this point in the history
We re-compute the generator expression by recompilcation. However, we do
descend to display the external inputs of the generator expression, and
ignore the placeholder.

If there was a function call on one or more placeholders, the
re-computation will fail with a cryptic message.

This patch fixes the issue.
  • Loading branch information
mristin committed Apr 3, 2021
1 parent d566488 commit f8d138e
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 0 deletions.
6 changes: 6 additions & 0 deletions icontract/_recompute.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,12 @@ def visit_Call(self, node: ast.Call) -> Any:
else:
kwargs[keyword.arg] = self.visit(node=keyword.value)

# If any of the positional or keyword arguments are placeholders, that means that we are re-computing a
# generator expression.
# As we re-compute them by re-compilation, we do not re-compute the individual calls here.
if PLACEHOLDER in args or PLACEHOLDER in kwargs.values():
return PLACEHOLDER

result = func(*args, **kwargs)

if inspect.iscoroutine(result):
Expand Down
44 changes: 44 additions & 0 deletions tests/test_represent.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,50 @@ def func(x: int) -> int:
'all(item == x for sublst in lst for item in sublst) was False\n'
'lst was [[1, 2], [3]]', tests.error.wo_mandatory_location(str(violation_error)))

def test_generator_expression_with_zip_and_multiple_for(self) -> None:
# Taken from a solution for Advent of Code 2020 day 11.
@icontract.ensure(
lambda layout, result:
all(cell == result_cell
for row, result_row in zip(layout, result[0])
for cell, result_cell in zip(row, result_row)
if cell == '.'),
"Floor remains floor"
)
def apply(layout: List[List[str]]) -> Tuple[List[List[str]], int]:
height = len(layout)
width = len(layout[0])

result = [[''] * width] * height
return result, 0

layout = [['L', '.', '#'], ['.', '#', '#']]

violation_error = None # type: Optional[icontract.ViolationError]
try:
_, _ = apply(layout=layout)
except icontract.ViolationError as err:
violation_error = err

self.assertIsNotNone(violation_error)

text = re.sub(r'<zip object at 0x[0-9a-fA-F]+>', '<zip object at some address>',
tests.error.wo_mandatory_location(str(violation_error)))

self.assertEqual(
textwrap.dedent('''\
Floor remains floor: all(cell == result_cell
for row, result_row in zip(layout, result[0])
for cell, result_cell in zip(row, result_row)
if cell == '.'):
all(cell == result_cell
for row, result_row in zip(layout, result[0])
for cell, result_cell in zip(row, result_row)
if cell == '.') was False
layout was [['L', '.', '#'], ['.', '#', '#']]
result was ([['', '', ''], ['', '', '']], 0)
zip(layout, result[0]) was <zip object at some address>'''), text)

def test_list_comprehension(self) -> None:
lst = [1, 2, 3]

Expand Down

0 comments on commit f8d138e

Please sign in to comment.