Skip to content

feat[lang]: allow DynArrays of length 0 - #5234

Merged
HodanPlodky merged 25 commits into
vyperlang:masterfrom
Sporarum:make-0-length
Sep 16, 2026
Merged

HodanPlodky merged 25 commits into
vyperlang:masterfrom
Sporarum:make-0-length

Conversation

@Sporarum

@Sporarum Sporarum commented Sep 3, 2026 •

Copy link
Copy Markdown
Collaborator

What I did

Allow DynArrays of length 0, including in user-written annotations
Change the type of [] to DynArray[Never, 0] (was DynArray[Never, 1])

Note: Does not allow Bytes[0] and String[0] (a different PR can do that).

How I did it

Change bounds check on lengths for DynArrays from > to >=

How to verify it

pytest, also see new tests

Commit message

this commit allows DArrayT to have a length of zero, and changes the
type of `[]`  to `DArrayT(BottomT(), 0)` (was `DArrayT(BottomT(), 1)`).

Description for the changelog

Allow DynArray[T, 0] type (which is only inhabited by [])

Cute Animal Picture

Put a link to a cute animal picture inside the parenthesis-->

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

Gas Changes

No changes detected.

Summary

  • Total tests measured: 560
  • Changed: 0
  • Regressions (gas up): 0
  • Improvements (gas down): 0
  • New tests: 0
  • Deleted tests: 0
  • Newly failing: 0
  • Newly passing: 0

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c7cda67d1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread vyper/codegen/ir_node.py
@github-actions

github-actions Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

📊 Bytecode Size Changes (venom)

No changes detected.

Full bytecode sizes

Contract legacy-O2 legacy-Os -O2 -O3 -Os
curvefi/legacy/CurveStableSwapMetaNG.vy 24941 23567 19769 19059 18523
curvefi/amm/stableswap/meta_implementation/meta_implementation_v_700.vy 23599 22794 19565 18627 18314
curvefi/amm/stableswap/implementation/implementation_v_700.vy 24951 23758 19188 18363 18019
curvefi/legacy/CurveStableSwapNG.vy 24462 23287 18749 17981 17636
curvefi/amm/tricryptoswap/implementation/implementation_v_200.vy 20724 19959 17250 16689 16325
curvefi/amm/twocryptoswap/implementation/implementation_v_210.vy 17634 16894 14958 14376 14027
yearnfi/VaultV3.vy 19972 19063 14739 13818 13269
curvefi/legacy/CurveCryptoSwap2.vy 18947 18382 14619 14129 13954
yearnfi/VaultV2.vy 16676 15763 13258 12466 12049
curvefi/amm/stableswap/factory/factory_v_100.vy 14558 13978 11852 10780 10880
curvefi/gauge/child_gauge/implementation/implementation_v_110.vy 12338 11561 9781 9184 8795
curvefi/amm/stableswap/views/views_v_120.vy 12784 12368 9705 9059 9294
curvefi/gauge/child_gauge/implementation/implementation_v_100.vy 12017 11249 9514 8924 8538
curvefi/amm/tricryptoswap/math/math_v_200.vy 11189 11126 9029 8144 8170
curvefi/legacy/CurveCryptoMathOptimized3.vy 11188 11125 9028 8144 8170
curvefi/gauge/child_gauge/implementation/implementation_v_020.vy 10665 9947 8626 8100 7714
curvefi/registries/metaregistry/metaregistry_v_110.vy 7590 6732 6491 5710 5603
curvefi/helpers/router/router_v_110.vy 6717 6717 6251 5733 6035
curvefi/amm/tricryptoswap/views/views_v_200.vy 7821 7776 6111 5896 6045
curvefi/helpers/stable_swap_meta_zap/stable_swap_meta_zap_v_100.vy 7302 7067 5877 5350 5610
curvefi/amm/twocryptoswap/views/views_v_200.vy 6991 6946 5680 5479 5614
curvefi/registries/metaregistry/registry_handlers/stableswap/handler_v_110.vy 6633 6259 5533 4695 5238
curvefi/amm/twocryptoswap/math/math_v_210.vy 6800 6800 5506 5012 5039
curvefi/amm/twocryptoswap/factory/factory_v_200.vy 5540 5252 4617 3917 4047
curvefi/amm/tricryptoswap/factory/factory_v_200.vy 5246 5021 4483 3890 4020
curvefi/gauge/child_gauge/factory/factory_v_201.vy 4844 4547 3901 3675 3511
curvefi/registries/metaregistry/registry_handlers/tricryptoswap/handler_v_110.vy 4241 3939 3718 3334 3429
curvefi/registries/metaregistry/registry_handlers/twocryptoswap/handler_v_110.vy 4186 3884 3671 3251 3329
curvefi/gauge/child_gauge/factory/factory_v_100.vy 4183 3914 3408 3144 2971
yearnfi/VaultFactory.vy 3765 3617 2936 2158 2461
curvefi/registries/address_provider/address_provider_v_201.vy 2973 2782 2613 2440 2353
curvefi/helpers/rate_provider/rate_provider_v_101.vy 3260 3260 2535 2263 2296
curvefi/amm/stableswap/math/math_v_100.vy 3067 3046 2458 2253 2310
curvefi/helpers/rate_provider/rate_provider_v_100.vy 2847 2841 2273 1954 1974
curvefi/helpers/deposit_and_stake_zap/deposit_and_stake_zap_v_100.vy 2322 2316 1782 1611 1670
curvefi/governance/relayer/taiko/relayer_v_001.vy 2068 2064 1731 1510 1558
curvefi/governance/relayer/polygon_cdk/relayer_v_101.vy 1556 1523 1530 1324 1347
curvefi/governance/relayer/arb_orbit/relayer_v_101.vy 1266 1262 1242 1066 1115
curvefi/governance/relayer/op_stack/relayer_v_101.vy 1186 1182 1183 1014 1056
curvefi/governance/relayer/not_rollup/relayer_v_100.vy 1168 1153 1174 1011 1037
curvefi/governance/vault/vault_v_100.vy 964 941 862 823 839
curvefi/governance/relayer/relayer_v_100.vy 496 496 593 490 503
curvefi/governance/agent/agent_v_100.vy 541 541 430 402 406
curvefi/governance/agent/agent_v_101.vy 541 541 430 402 406

@charles-cooper charles-cooper left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

excellent!

@harkal

harkal commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

@Sporarum tests seem to fail under venom

Comment thread vyper/codegen/abi_encoder.py Outdated
Comment thread vyper/ir/compile_ir.py Outdated
Comment thread vyper/ir/compile_ir.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 06d3ae26ec

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +123 to 124
if not 0 <= length < 2**256:
raise InvalidType("Array length is invalid")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Materialize conditional zero-length DynArrays

With the default legacy backend, a valid expression such as return [] if cond else [] from a function returning DynArray[uint256, 0] (or assigning that expression to such a local) reaches codegen and raises CompilerPanic: cannot dereference non-pointer type. Both arms now have the accepted zero-capacity type and remain ~empty non-pointers, so the ternary retains them without materializing memory and ABI encoding later tries to load their length. Simple return [] and the Venom backend compile successfully, making this a legacy-codegen failure for the newly enabled type.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to the current changes, see #5199

Comment thread vyper/ir/compile_ir.py Outdated

# assert rounds <= round_bound
if rounds != rounds_bound:
if rounds != rounds_bound or rounds_bound.value == 0:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the new clause in the condition needs a detailed explanation for why rounds_bound.value == 0 requires the code generated below

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See 0b52720

@charles-cooper charles-cooper changed the title feat[lang]: allow DynArrays of length 0 feat[lang]: allow DynArrays of length 0 Sep 11, 2026

@charles-cooper charles-cooper left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. mergeable pending 2nd pair of eyes from @HodanPlodky

@Sporarum

Sporarum commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

It turned out to be simpler to refactor the code than to explain the existing one, see: 0b52720

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b652989d47

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread vyper/ir/compile_ir.py
Comment on lines +377 to +379
if isinstance(rounds.value, int):
assert isinstance(rounds_bound.value, int)
assert 0 <= rounds.value <= rounds_bound.value

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve runtime validation for literal repeat counts

For documented direct VyperIR inputs, a literal rounds value is not otherwise constrained by IRnode.from_list; for example, repeat(i, 0, 1, 0, body) was previously compiled with the documented runtime rounds <= rounds_bound assertion and therefore reverted when executed. This new assertion instead aborts compilation for any literal count above its bound (and for negative literals), so invalid untrusted/count-derived IR can no longer be compiled into a safely reverting contract. Keep the runtime check for out-of-range literals or reject them with a user-facing IR validation error before lowering.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@charles-cooper @HodanPlodky do we care about direct VyperIR inputs ?
(by care I mean preserve backwards compat)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried it and it fails even on master in vyper/codegen/ir_node.py. There is a check repeat without 0 bound so why would this be a change against the current version, what am I missing?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no i think the bot comment is being too pedantic

@HodanPlodky HodanPlodky left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one test just seems bit odd to me but otherwise looks good



@pytest.mark.xfail(raises=InvalidOperation)
def test_index_all_empty_lists_variable_index(get_contract, tx_failed):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we planning to support this in future, since the return of the single of DynArray[Never, 0] will be handled correctly?
And also

@external
def foo(i: uint256) -> DynArray[uint256, 5]:
    return [[], [1]][i]

would work as I would expect so what is the blocking this one to compile?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we planning to support this in future, since the return of the single of DynArray[Never, 0] will be handled correctly?

I think we should

@external
def foo(i: uint256) -> DynArray[uint256, 5]:
    return [[], [1]][i]

would work as I would expect so what is the blocking this one to compile?

It does work as you would expect (0 -> [], 1 -> [1], 2+ -> reverts)

The issue is that we use the element type's abi to compile even empty lists
With [[], [1]] we infer type SArrayT(DArrayT(uint256, 1), 2) for the whole expression, so [] is compiled as a DArrayT(uint256, 2) (or , 0], doesn't matter)
But with [[], []] we infer the type to SArrayT(DArrayT(BottomT, 0), 2), so we don't know how to compile the [] elements

This can be fixed in two ways:

  1. For nodes of type DArrayT[T, 0], don't fetch the abi-encoding of the elements, just output an empty (discussed in private with @harkal as a possibility)
  2. Annotate nodes with the expected type, and not the infered type, requires/part of Simplify Typer Internals #5017

And I think we should do both (but outside of scope for this PR)

@HodanPlodky
HodanPlodky merged commit 6f1aefb into vyperlang:master Sep 16, 2026
171 checks passed
@Sporarum
Sporarum deleted the make-0-length branch September 17, 2026 07:05

@pcaversaccio pcaversaccio left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm, if you use empty it doesn't compile:

@external
@pure
def foo(x: DynArray[uint256, empty(uint256)]) -> DynArray[uint256, empty(uint256)]:
    return x

@charles-cooper

Copy link
Copy Markdown
Member

hmmm, if you use empty it doesn't compile:

@external
@pure
def foo(x: DynArray[uint256, empty(uint256)]) -> DynArray[uint256, empty(uint256)]:
    return x

separate issue, having to do with compile-time elaboration of empty()

@pcaversaccio

Copy link
Copy Markdown
Member

hmmm, if you use empty it doesn't compile:

@external
@pure
def foo(x: DynArray[uint256, empty(uint256)]) -> DynArray[uint256, empty(uint256)]:
    return x

separate issue, having to do with compile-time elaboration of empty()

do we have an open issue on this? i found my old one here: #3480

@charles-cooper

Copy link
Copy Markdown
Member

i think you could create a new issue -- that one is not about availability of empty in the type system

@pcaversaccio

Copy link
Copy Markdown
Member

i think you could create a new issue -- that one is not about availability of empty in the type system

#5272

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants