diff --git a/tests/parser/syntax/test_for_range.py b/tests/parser/syntax/test_for_range.py index b2a9491058..fdd1cef347 100644 --- a/tests/parser/syntax/test_for_range.py +++ b/tests/parser/syntax/test_for_range.py @@ -57,3 +57,30 @@ def kick_foos(): @pytest.mark.parametrize("good_code", valid_list) def test_range_success(good_code): assert compiler.compile_code(good_code) is not None + + +fail_list = [ + # Cannot call `pop()` in for range because it modifies state + ( + """ +arr: DynArray[uint256, 10] + +@external +def test()-> (DynArray[uint256, 6], DynArray[uint256, 10]): + b: DynArray[uint256, 6] = [] + + self.arr = [1,0] + + for i in range(self.arr.pop(), self.arr.pop() + 2): + b.append(i) + + return b, self.arr + """, + ImmutableViolation, + ) +] + + +@pytest.mark.parametrize("bad_code,exc", fail_list) +def test_range_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_code, exc): + assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code), exc) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 23ccc216a6..f5c1138437 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -343,6 +343,15 @@ def visit_For(self, node): ) validate_call_args(node.iter, (1, 2)) + # Check if pop` operation is used in `range` + darray_pop_nodes = node.iter.get_descendants(vy_ast.Attribute, {"attr": "pop"}) + if len(darray_pop_nodes) > 0: + pop_node = darray_pop_nodes.pop() + raise ImmutableViolation( + "Cannot call `pop` inside for loop because it modifies the dynamic array", + pop_node, + ) + args = node.iter.args if len(args) == 1: # range(CONSTANT)