Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add extend for dynamic arrays #2976

Open
wants to merge 48 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
57ddd0e
move pop to builtin
tserg Jul 4, 2022
5e5c74a
move append to builtin
tserg Jul 4, 2022
ae4c8f8
add extend codegen
tserg Jul 19, 2022
f43f5de
add tests
tserg Jul 19, 2022
128b918
fix lint
tserg Jul 19, 2022
9e823cc
fix test
tserg Jul 19, 2022
897caa3
add assertion for dst darray bounds; add complex tests
tserg Jul 21, 2022
bdc513d
add invalid length test
tserg Jul 21, 2022
9ef6204
add maxlen clamp
tserg Jul 21, 2022
ec22b42
add test for maxlen clamp
tserg Jul 21, 2022
aea039b
refactor ir
tserg Jul 21, 2022
48028b1
add partially filled src darray test
tserg Jul 21, 2022
cfd4a56
more refactoring of IR
tserg Jul 21, 2022
0ccb42f
Merge branch 'master' of https://github.com/vyperlang/vyper into feat…
tserg Jul 21, 2022
bb168db
refactor codegen
tserg Jul 22, 2022
def2da3
add empty test case
tserg Jul 22, 2022
5d4912d
fix lint
tserg Jul 22, 2022
ec65d6f
delete truncation branch
tserg Jul 22, 2022
e9e0362
rewrite loop var to start from 0
tserg Jul 22, 2022
007713d
refactor to _dynarray_make_setter
tserg Jul 23, 2022
dbfa5dd
fix lint
tserg Jul 23, 2022
2a06b6f
fix lint
tserg Jul 23, 2022
9e0a8f0
add docs
tserg Jul 23, 2022
00f6812
remove no_optimize check for test
tserg Jul 23, 2022
733d819
refactor to store_length kwarg
tserg Jul 23, 2022
e1e2ae5
Revert "remove no_optimize check for test"
tserg Jul 23, 2022
dd0eece
remove unnecessary cache
tserg Jul 23, 2022
d905098
refactor _dynarray_make_setter
tserg Jul 26, 2022
f253caf
refactor wip
tserg Jul 26, 2022
ce0b111
wip refactor
tserg Jul 26, 2022
72ec9c1
cleanup; add assertion
tserg Jul 26, 2022
f7267ac
undo unnecessary changes
tserg Jul 26, 2022
ddcafe8
refactor from code review
tserg Jul 27, 2022
d1e70bf
Merge branch 'master' of https://github.com/vyperlang/vyper into feat…
tserg Jul 28, 2022
c365af5
remove unnecessary caches
tserg Jul 28, 2022
67f8b4e
move dynarray fns to builtin functions
tserg Aug 4, 2022
db0be2d
Merge branch 'master' of https://github.com/vyperlang/vyper into feat…
tserg Aug 4, 2022
d33919a
fix errant comment
tserg Aug 5, 2022
45b61ff
coalesce branch in dynarray_make_setter
tserg Aug 5, 2022
af255de
fix comment
tserg Aug 5, 2022
e3487b0
add dst_ofst kwarg
tserg Aug 5, 2022
577090d
cast dst as static kwarg
tserg Aug 5, 2022
dccbe67
refactor; change return_popped_item to kwarg
tserg Aug 5, 2022
967e6a0
fix lint
tserg Aug 5, 2022
066695c
Merge branch 'master' of https://github.com/vyperlang/vyper into feat…
tserg Aug 9, 2022
a955175
fix merge conflict
tserg Aug 9, 2022
10299bb
Merge branch 'master' of https://github.com/vyperlang/vyper into feat…
tserg Aug 10, 2022
96c680e
fix no_optimize tests
tserg Aug 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,14 @@ Dynamic arrays represent bounded arrays whose length can be modified at runtime,
exampleList.append(120) # exampleList now has length 2
exampleList.append(356) # exampleList now has length 3
# exampleList.append(1) # would revert!
anotherList: DynArray[int128, 3] = []
anotherList.extend(exampleList) # anotherList now has length 3
longerList: DynArray[int128, 5] = [1]
longerList.extend(exampleList) # longerList now has length 4
failList: DynArray[int128, 3] = [1]
failList.extend(exampleList) # would revert because max length of failList is exceeded!
shortList: DynArray[int128, 2] = []
shortList.extend(exampleList) # will not compile!

myValue: int128 = exampleList.pop() # myValue == 356, exampleList now has length 2

Expand Down
276 changes: 267 additions & 9 deletions tests/parser/types/test_dynamic_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -1252,6 +1252,269 @@ def test_append_pop_complex(get_contract, assert_tx_failed, code_template, check
assert c.foo(test_data) == expected_result


@pytest.mark.parametrize("subtyp", ["uint8", "int128", "uint256"])
def test_extend_literal(get_contract, subtyp):
data = [1, 2, 3]
if subtyp == "int128":
data = [-1, 2, 3]
code = f"""
@external
def foo() -> DynArray[{subtyp}, 3]:
x: DynArray[{subtyp}, 3] = []
a: DynArray[{subtyp}, 3] = [{data[0]}]
x.extend(a)
b: DynArray[{subtyp}, 3] = [{data[1]}]
x.extend(b)
c: DynArray[{subtyp}, 3] = [{data[2]}]
x.extend(c)
return x
"""
c = get_contract(code)
assert c.foo() == data


def test_extend_invalid_length(get_contract, assert_compile_failed):
code = """
@external
def foo() -> DynArray[uint256, 3]:
x: DynArray[uint256, 3] = []
y: DynArray[uint256, 4] = []
x.extend(y)
return x
"""
assert_compile_failed(lambda: get_contract(code), TypeMismatch)


def test_extend_valid_length(get_contract):
code = """
@external
def foo(y: DynArray[uint256, 3]) -> DynArray[uint256, 5]:
x: DynArray[uint256, 5] = [1, 2]
x.extend(y)
return x
"""
c = get_contract(code)
assert c.foo([3, 4]) == [1, 2, 3, 4]


def test_extend_empty(get_contract):
code = """
@external
def foo(y: DynArray[uint256, 3]) -> DynArray[uint256, 3]:
x: DynArray[uint256, 3] = [1, 2, 3]
x.extend(y)
return x
"""
c = get_contract(code)
assert c.foo([]) == [1, 2, 3]


def test_extend_length_clamp(get_contract, assert_tx_failed):
code = """
@external
def foo(y: DynArray[uint256, 2]) -> DynArray[uint256, 3]:
x: DynArray[uint256, 3] = [1, 2]
x.extend(y)
return x
"""
c = get_contract(code)
assert_tx_failed(lambda: c.foo([3, 4]))


extend_pop_tests = [
(
"""
my_array: DynArray[uint256, 5]
@external
def foo(xs: DynArray[uint256, 5]) -> DynArray[uint256, 5]:
self.my_array.extend(xs)
return self.my_array
""",
lambda xs: xs,
),
(
"""
my_array: DynArray[uint256, 5]
@external
def foo(xs: DynArray[uint256, 5]) -> DynArray[uint256, 5]:
self.my_array.extend(xs)
for x in xs:
self.my_array.pop()
return self.my_array
""",
lambda xs: [],
),
# check order of evaluation.
(
"""
my_array: DynArray[uint256, 5]
@external
def foo(xs: DynArray[uint256, 5]) -> (DynArray[uint256, 5], uint256):
self.my_array.extend(xs)
return self.my_array, self.my_array.pop()
""",
lambda xs: None if len(xs) == 0 else [xs, xs[-1]],
),
# check order of evaluation.
(
"""
my_array: DynArray[uint256, 5]
@external
def foo(xs: DynArray[uint256, 5]) -> (uint256, DynArray[uint256, 5]):
self.my_array.extend(xs)
return self.my_array.pop(), self.my_array
""",
lambda xs: None if len(xs) == 0 else [xs[-1], xs[:-1]],
),
# test memory arrays
(
"""
@external
def foo(xs: DynArray[uint256, 5]) -> DynArray[uint256, 5]:
ys: DynArray[uint256, 5] = []
ys.extend(xs)
return ys
""",
lambda xs: xs,
),
# pop to 0 elems
(
"""
@external
def foo(xs: DynArray[uint256, 5]) -> DynArray[uint256, 5]:
ys: DynArray[uint256, 5] = []
ys.extend(xs)
for x in xs:
ys.pop()
return ys
""",
lambda xs: [],
),
# check underflow
(
"""
@external
def foo(xs: DynArray[uint256, 5]) -> DynArray[uint256, 5]:
ys: DynArray[uint256, 5] = []
ys.extend(xs)
for x in xs:
ys.pop()
ys.pop() # fail
return ys
""",
lambda xs: None,
),
]


@pytest.mark.parametrize("code,check_result", extend_pop_tests)
# TODO change this to fuzz random data
@pytest.mark.parametrize("test_data", [[1, 2, 3, 4, 5][:i] for i in range(6)])
def test_extend_pop(get_contract, assert_tx_failed, code, check_result, test_data):
c = get_contract(code)
expected_result = check_result(test_data)
if expected_result is None:
# None is sentinel to indicate txn should revert
assert_tx_failed(lambda: c.foo(test_data))
else:
assert c.foo(test_data) == expected_result


invalid_extend_pop = [
(
"""
my_array: DynArray[uint256, 5]
@external
def foo(xs: DynArray[uint256, 6]) -> DynArray[uint256, 5]:
self.my_array.extend(xs)
return self.my_array
""",
TypeMismatch, # Size of src darray is greater than dst darray
)
]


@pytest.mark.parametrize("code,exception_type", invalid_extend_pop)
def test_invalid_extend_pop(get_contract, assert_compile_failed, code, exception_type):
assert_compile_failed(lambda: get_contract(code), exception_type)


extend_pop_complex_tests = [
(
"""
@external
def foo(x: {typ}) -> DynArray[{typ}, 2]:
ys: DynArray[{typ}, 1] = []
temp: DynArray[{typ}, 1] = [x]
ys.extend(temp)
return ys
""",
lambda x: [x],
),
(
"""
my_array: DynArray[{typ}, 1]
@external
def foo(x: {typ}) -> DynArray[{typ}, 2]:
temp: DynArray[{typ}, 1] = [x]
self.my_array.extend(temp)
self.my_array.extend(temp) # fail
return self.my_array
""",
lambda x: None,
),
(
"""
my_array: DynArray[{typ}, 5]
@external
def foo(x: {typ}) -> (DynArray[{typ}, 5], {typ}):
temp: DynArray[{typ}, 1] = [x]
self.my_array.extend(temp)
return self.my_array, self.my_array.pop()
""",
lambda x: [[x], x],
),
(
"""
my_array: DynArray[{typ}, 5]
@external
def foo(x: {typ}) -> ({typ}, DynArray[{typ}, 5]):
temp: DynArray[{typ}, 1] = [x]
self.my_array.extend(temp)
return self.my_array.pop(), self.my_array
""",
lambda x: [x, []],
),
]


@pytest.mark.parametrize("code_template,check_result", extend_pop_complex_tests)
@pytest.mark.parametrize(
"subtype", ["uint256[3]", "DynArray[uint256,3]", "DynArray[uint8, 4]", "Foo"]
)
# TODO change this to fuzz random data
def test_extend_pop_complex(get_contract, assert_tx_failed, code_template, check_result, subtype):
code = code_template.format(typ=subtype)
test_data = [1, 2, 3]
if subtype == "Foo":
test_data = tuple(test_data)
struct_def = """
struct Foo:
x: uint256
y: uint256
z: uint256
"""
code = struct_def + "\n" + code

c = get_contract(code)
expected_result = check_result(test_data)
if expected_result is None:
# None is sentinel to indicatecool, I'd txn should revert
assert_tx_failed(lambda: c.foo(test_data))
else:
assert c.foo(test_data) == expected_result


def test_so_many_things_you_should_never_do(get_contract):
code = """
@internal
Expand Down Expand Up @@ -1523,7 +1786,7 @@ def bar(x: int128) -> DynArray[int128, 3]:
assert c.bar(7) == [7, 14]


def test_nested_struct_of_lists(get_contract, assert_compile_failed, no_optimize):
def test_nested_struct_of_lists(get_contract, assert_compile_failed):
code = """
struct nestedFoo:
a1: DynArray[DynArray[DynArray[uint256, 2], 2], 2]
Expand Down Expand Up @@ -1563,14 +1826,9 @@ def bar2() -> uint256:
newFoo.b1[1][0][0].a1[0][1][1] + \\
newFoo.b1[0][1][0].a1[0][0][0]
"""

if no_optimize:
# fails at assembly stage with too many stack variables
assert_compile_failed(lambda: get_contract(code), Exception)
else:
c = get_contract(code)
assert c.bar() == [[[3, 7], [7, 3]], [[7, 3], [0, 0]]]
assert c.bar2() == 0
c = get_contract(code)
assert c.bar() == [[[3, 7], [7, 3]], [[7, 3], [0, 0]]]
assert c.bar2() == 0


def test_tuple_of_lists(get_contract):
Expand Down
Loading