Skip to content

fix[lang]: fix list typing issues#5177

Open
Sporarum wants to merge 13 commits into
vyperlang:masterfrom
Sporarum:fix-list-issues
Open

fix[lang]: fix list typing issues#5177
Sporarum wants to merge 13 commits into
vyperlang:masterfrom
Sporarum:fix-list-issues

Conversation

@Sporarum

@Sporarum Sporarum commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

What I did

Fix: #5176
Fix #5161

More broadly, fixes get_common_types always selecting the first type, as opposed to the supremum of the two:

type A <: B

a: A
b: B

# before:
[a, b] # inferred to have element type `A` # incorrect, b does not have type A
[b, a] # inferred to have element type `B`

# after:
[a, b] # inferred to have element type `B`
[b, a] # inferred to have element type `B`

As a workaround, secondary checks where performed in validate_expected_type and in visit_Compare (a < b case), as these are no longer required, they are removed by this PR.

Independently, empty list literals ([]) were inferred to be a list of all built-in types, this lead to issues when combining them with lists of user-made types.
This PR fixes that, now empty list literals are compatible with lists of any type.

How I did it

To solve the get_common_types issue, it has been refactored to always return the bigger of the pair of matching types.

Added the Never bottom type: a type which is a subtype of every type. As a consequence, the supremum of Never with a type T will always be T.
Given lists are covariant, a list of Never can be widened to a list of any required type.
Therefore, by having [] have type DynArray[Never, 1], it is a valid DynArray[T, n] for any T and n.

How to verify it

pytest

Commit message

'get_common_types' is used to compute a set of types such that all given
nodes conform to them. this is notably used to determine the type of
list literals. in the presence of covariant types like `Bytes`, the
previous logic always favored the first occurrence of the type, instead
of the supremum. this lead to issues where the inferred type was
incorrect, see added test for an example. additional logic was added in
'visit_Compare' for the case 'a < b' where 'a' and 'b' are bytestrings.
this commit fixes 'get_common_types', and remove the now-obsolete logic
in 'visit_Compare'.

this commit introduces `BottomT` (displayed to users as `Never`), the
bottom type: a subtype of every other type.

an empty list literal `[]` was inferred to be a `DynArray` over every
built-in type, so it could not be combined with lists of user-defined
types, and also caused issue when combining different instances of
covariant types. this is solved by instead inferring it to have type
`DynArray[Never, 1]`, since it is a subtype of all `DynArray[T, n]`
`get_common_types` will always use the other type when combining (sub)
lists.

Description for the changelog

Fixed some issues related to computing types of nested lists.

Cute Animal Picture

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

Comment on lines +353 to +355
if len(node.elements) == 0:
# can't have an empty SArrayT
return [DArrayT(BottomT(), 1)]

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.

#5175 (comment)

P2 Badge Reject member calls on empty list literals

Because [] now has one exact dynamic-array type, invalid code such as x: uint256 = [].pop() can resolve the pop member and its Never return type type-checks as uint256; the function visitor then treats the list literal as the modified target and hits the _handle_modification() assertion because literals have no VarAccess. Before this change the same member lookup was rejected as an ambiguous empty-list type, so this should be diagnosed instead of reaching an internal compiler assertion.

@Sporarum Sporarum Jul 1, 2026

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.

def foo():
    tmp: uint256 = [empty(uint256)].pop() # StructureException: Ambiguous type

Where the ambiguity is between DynArray[uint256, 1] and uint256[1].

So there are two ways of solving this:

  1. Implementing some sort of l-value system, required for Assign to call-return index crashes compiler #5138 and Writing self.balance panics under Venom codegen #5151 anyways
  2. Making [] also a Never[0]

Both sounds like they would be useful, but 1. solves a bigger and more serious issue.
I added an xfail test for this.

Comment on lines +353 to +355
if len(node.elements) == 0:
# can't have an empty SArrayT
return [DArrayT(BottomT(), 1)]

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.

#5175 (comment)

P2 Badge Reject untyped empty list varargs before ABI typing

For vararg builtins that require an exact argument type, such as abi_encode([]), this makes the empty list exact as DynArray[Never, 1] instead of ambiguous. ABIEncode.fetch_call_return() then calls arg_t.abi_type on that inferred argument, which descends into BottomT.abi_type and raises a compiler panic; the untyped empty list should still be rejected with a normal diagnostic unless an expected element type is available.

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.

Added an error message instead

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Gas Changes

No changes detected.

Summary

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

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

📊 Bytecode Size Changes (venom)

No changes detected.

Full bytecode sizes

Contract legacy-O2 legacy-Os -O2 -O3 -Os
curvefi/amm/stableswap/meta_implementation/meta_implementation_v_700.vy 23610 22805 20786 19755 19315
curvefi/legacy/CurveStableSwapMetaNG.vy 24952 23578 20490 19871 19175
curvefi/amm/stableswap/implementation/implementation_v_700.vy 24962 23769 20264 19393 18845
curvefi/legacy/CurveStableSwapNG.vy 24473 23298 19677 18844 18314
curvefi/amm/tricryptoswap/implementation/implementation_v_200.vy 20724 19959 18020 17518 16961
yearnfi/VaultV3.vy 19972 19063 17017 15131 14477
curvefi/amm/twocryptoswap/implementation/implementation_v_210.vy 17634 16894 15685 15132 14648
curvefi/legacy/CurveCryptoSwap2.vy 18947 18382 15634 15092 14711
yearnfi/VaultV2.vy 16676 15763 13863 13405 12574
curvefi/amm/stableswap/factory/factory_v_100.vy 14558 13978 13317 11825 11788
curvefi/gauge/child_gauge/implementation/implementation_v_110.vy 12338 11561 10270 9775 9182
curvefi/gauge/child_gauge/implementation/implementation_v_100.vy 12017 11249 9998 9502 8920
curvefi/amm/stableswap/views/views_v_120.vy 12784 12368 9970 9413 9532
curvefi/amm/tricryptoswap/math/math_v_200.vy 11189 11126 9520 8162 8424
curvefi/legacy/CurveCryptoMathOptimized3.vy 11188 11125 9519 8162 8424
curvefi/gauge/child_gauge/implementation/implementation_v_020.vy 10665 9947 9013 8565 8028
curvefi/helpers/router/router_v_110.vy 6717 6717 7003 6376 6607
curvefi/amm/tricryptoswap/views/views_v_200.vy 7821 7776 6697 6445 6514
curvefi/registries/metaregistry/metaregistry_v_110.vy 7590 6732 6651 5888 5727
curvefi/helpers/stable_swap_meta_zap/stable_swap_meta_zap_v_100.vy 7302 7067 6379 6012 6114
curvefi/amm/twocryptoswap/views/views_v_200.vy 6991 6946 6272 6026 6089
curvefi/amm/twocryptoswap/factory/factory_v_200.vy 5540 5252 5994 4697 4845
curvefi/amm/tricryptoswap/factory/factory_v_200.vy 5246 5021 5923 4926 5039
curvefi/registries/metaregistry/registry_handlers/stableswap/handler_v_110.vy 6633 6259 5859 5094 5562
curvefi/amm/twocryptoswap/math/math_v_210.vy 6800 6800 5581 5107 5112
curvefi/gauge/child_gauge/factory/factory_v_201.vy 4844 4547 4266 4014 3780
yearnfi/VaultFactory.vy 3765 3617 3985 2505 2965
curvefi/registries/metaregistry/registry_handlers/tricryptoswap/handler_v_110.vy 4241 3939 3838 3547 3547
curvefi/registries/metaregistry/registry_handlers/twocryptoswap/handler_v_110.vy 4186 3884 3777 3417 3433
curvefi/gauge/child_gauge/factory/factory_v_100.vy 4183 3914 3694 3428 3213
curvefi/helpers/rate_provider/rate_provider_v_101.vy 3260 3260 2779 2437 2450
curvefi/registries/address_provider/address_provider_v_201.vy 2973 2782 2713 2569 2424
curvefi/amm/stableswap/math/math_v_100.vy 3067 3046 2598 2403 2423
curvefi/helpers/rate_provider/rate_provider_v_100.vy 2847 2841 2357 2050 2058
curvefi/helpers/deposit_and_stake_zap/deposit_and_stake_zap_v_100.vy 2322 2316 2123 1889 1921
curvefi/governance/relayer/taiko/relayer_v_001.vy 2068 2064 1784 1579 1612
curvefi/governance/relayer/polygon_cdk/relayer_v_101.vy 1556 1523 1587 1405 1406
curvefi/governance/relayer/arb_orbit/relayer_v_101.vy 1266 1262 1280 1124 1155
curvefi/governance/relayer/op_stack/relayer_v_101.vy 1186 1182 1219 1066 1094
curvefi/governance/relayer/not_rollup/relayer_v_100.vy 1168 1153 1208 1061 1073
curvefi/governance/vault/vault_v_100.vy 964 941 885 866 864
curvefi/governance/relayer/relayer_v_100.vy 496 496 600 505 510
curvefi/governance/agent/agent_v_100.vy 541 541 443 415 419
curvefi/governance/agent/agent_v_101.vy 541 541 443 415 419

@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: 63d96353ad

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread vyper/semantics/analysis/utils.py Outdated
Comment on lines +507 to +509
if c.is_subtype_of(t):
tmp.append(t)
break

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 contextual integer types after empty sublists

When an empty sublist is the first element, common_types starts as DynArray[Never, 1]; this branch appends only the first later type that can contain it and then breaks. For a literal like [[], [1]], the first matching dynamic-array type is the default positive-integer choice (e.g. DynArray[uint256, 1]), so other valid contextual choices are discarded and assignments such as tmp: DynArray[DynArray[int128, 1], 2] = [[], [1]] are rejected depending only on element order ([[1], []] still keeps the int128 alternative).

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.

This was confusingly worded, but I think what this means is: when we have [a, b], for each type of a we loop over all types of b, and we break at the first match, thus shadowing potential further matches.
Before since [] had a type for each builtin type, this "absorbtion" was never noticed, there could never be 3 types among the types of a and b that such that a V could form.
In what codex says, that would be Never for a and uint256 and int128 for b.
The loop would break at Never <: uint256, and never add int128 to the list.

I fixed it by not breaking on a match.
This can lead to an explosion in the number of types of the list, but only if one of the elements has at least 2 types such that one is a subtype of the other:

[_: {Bytes[1], Bytes[2]}, _: {Bytes[3], Bytes[4]}]
# would be inferred to have type:
{
  Bytes[3], # Bytes[1] <: Bytes[3]
  Bytes[4], # Bytes[1] <: Bytes[4]
  Bytes[3], # Bytes[2] <: Bytes[3]
  Bytes[4], # Bytes[2] <: Bytes[4]
}

I was only able to find one kind of expression with redundant types like this: [<expr>][0]: {<types of expr>, <types of expr>}. Due to [<expr>] having a DArrayT and SArrayT with the same element type.
Given how contrived this is, and that the only impact is time/space used for combination, this is fine for now.
But we should avoiding looping entirely, by having a single type per expression (see #5017).

Comment on lines +353 to +355
if len(node.elements) == 0:
# can't have an empty SArrayT
return [DArrayT(BottomT(), 1)]

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 expected types for all-empty subscripted arrays

Because every empty literal is materialized as DynArray[Never, 1], an all-empty nested expression selected through another expression keeps that bottom element type even after contextual validation accepts it. For example, tmp: DynArray[Foo, 5] = [[], []][0] type-checks as an empty array of Foo, but parse_Subscript still returns an IR node typed as DynArray[Never, 1], so make_setter recursively rejects the Never element against the struct element type during codegen instead of compiling the empty array.

Useful? React with 👍 / 👎.

Comment thread vyper/semantics/analysis/utils.py

@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: 68ffe0ad32

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tests/functional/syntax/test_dynamic_array.py
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.

Nonsensical TypeMismatch when nesting arrays Empty lists are not considered "lists of anything"

1 participant