From 974dd55e28c15443bc15b391bc6806e6d0203534 Mon Sep 17 00:00:00 2001 From: z80 Date: Sat, 30 Aug 2025 00:16:32 -0400 Subject: [PATCH 1/8] feat(flag): require iteration via Flag.__values__ - Add Flag.__values__ type member returning SArray[Flag] - Codegen: materialize Flag.__values__ as static array of flag bitmasks - Analysis: remove iteration over Flag type; require array/list iterator - Codegen: drop For-over-flag-type path; use list/array iteration only - Docs: update iteration section to use Flag.__values__ - Tests: update flag iteration tests to use __values__ BREAKING CHANGE: Iterating a flag type now requires using Flag.__values__ rather than the type itself.\n --- agents.md | 45 ++++++ docs/types.rst | 24 +++ .../features/test_flag_iteration_type.py | 143 ++++++++++++++++++ vyper/codegen/expr.py | 17 +++ vyper/codegen/stmt.py | 5 +- vyper/semantics/analysis/local.py | 1 + vyper/semantics/types/user.py | 9 +- 7 files changed, 241 insertions(+), 3 deletions(-) create mode 100644 agents.md create mode 100644 tests/functional/codegen/features/test_flag_iteration_type.py diff --git a/agents.md b/agents.md new file mode 100644 index 0000000000..6e5b2f3267 --- /dev/null +++ b/agents.md @@ -0,0 +1,45 @@ +Vyper Dev Environment: Virtualenv + Tests + +Quick TL;DR +- Use the existing virtualenv: `. .venv/bin/activate` and run tests: `pytest` or `./quicktest.sh`. +- If creating fresh: `python3.12 -m venv .venv && . .venv/bin/activate && pip -U pip wheel setuptools && pip install -e .[test]`. + +Supported Python +- Project supports Python 3.10–3.13. Prefer 3.12 for parity with CI. + +System Prereqs (Linux) +- git (setuptools-scm reads the commit hash) +- build tools for any wheels that don’t have prebuilt binaries: `build-essential` (Debian/Ubuntu). Optional: `pkg-config`. +- If network-restricted in the CLI sandbox, request approval for network before running `pip install`. + +Set Up From Scratch +1) Create venv + - `python3.12 -m venv .venv` + - `source .venv/bin/activate` + - Upgrade basics: `pip install -U pip wheel setuptools` + +2) Install package + test deps + - `pip install -e .[test]` + Includes: pytest, xdist, instafail, split, hypothesis[lark], py-evm, eth-account, hexbytes, pyrevm, etc. + - If setuptools-scm missing (rare): `pip install setuptools_scm` + +3) Verify compiler import + - `python -c "import vyper; print(vyper.__version__)"` + - If you see `ModuleNotFoundError: No module named 'Crypto'`, install `pycryptodome` (part of install_requires): + `pip install pycryptodome` + +Run Tests +- Full suite: `pytest` (parallel by default via xdist) or `./quicktest.sh` (bails on first failure). +- Faster dev loop: `./quicktest.sh -m "not fuzzing" -n0` +- Single test file: `pytest tests/functional/codegen/features/test_flag_iteration_type.py -q` +- Single test: `pytest tests/.../test_flag_iteration_type.py::test_iterate_over_flag_type -q` + +Notes +- The repo writes commit hash into `vyper/vyper_git_commithash.txt` during editable install; ensure `git` is available. +- Some optional tests are marked `hevm`; they may require external tooling and are not run by default. +- If running inside the Codex CLI with network restrictions, most installs require network; ask for approval when needed. + +Known Pitfalls +- Using system Python (e.g., 3.13) without the venv can miss dependencies (e.g., `Crypto`). Always activate `.venv`. +- If pip tries to build wheels from source and fails, install system build tools and retry (`build-essential`, `pkg-config`). + diff --git a/docs/types.rst b/docs/types.rst index 807c83848f..c6c7b89257 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -475,6 +475,30 @@ The following code uses bitwise operations to add and revoke permissions from a ret ^= Roles.USER # flip the user bit between 0 and 1 return ret +Iteration +^^^^^^^^^ + +You can iterate over all members of a flag type via the special type member ``__values__``. The loop variable must be annotated with the same flag type. + +The iteration order follows the declaration order (i.e. ascending bit index from left to right), and the number of iterations is statically bounded by the number of members in the flag. + +.. code-block:: vyper + + flag Permission: + READ + WRITE + EXECUTE + + @external + @pure + def all_mask() -> uint256: + acc: uint256 = 0 + for p: Permission in Permission.__values__: + acc = acc | convert(p, uint256) + return acc # 1 | 2 | 4 == 7 + +Attempting to iterate a flag type with a loop variable of a different type is a type error. + .. index:: !reference Reference Types diff --git a/tests/functional/codegen/features/test_flag_iteration_type.py b/tests/functional/codegen/features/test_flag_iteration_type.py new file mode 100644 index 0000000000..0f04d5438a --- /dev/null +++ b/tests/functional/codegen/features/test_flag_iteration_type.py @@ -0,0 +1,143 @@ +def test_iterate_over_flag_type(get_contract): + code = """ +flag Permission: + A + B + C + +@pure +@external +def sum_mask() -> uint256: + acc: uint256 = 0 + for p: Permission in Permission.__values__: + acc = acc | convert(p, uint256) + return acc +""" + c = get_contract(code) + # 1 | 2 | 4 = 7 + assert c.sum_mask() == 7 + + +def test_iterate_over_flag_type_count(get_contract): + code = """ +flag Permission: + A + B + C + D + +@pure +@external +def count() -> uint256: + cnt: uint256 = 0 + for p: Permission in Permission.__values__: + cnt += 1 + return cnt +""" + c = get_contract(code) + assert c.count() == 4 + + +def test_iterate_over_flag_type_order(get_contract): + code = """ +flag Permission: + A + B + C + D + +@pure +@external +def order_sum() -> uint256: + acc: uint256 = 0 + idx: uint256 = 0 + for p: Permission in Permission.__values__: + acc = acc + (convert(p, uint256) << idx) + idx += 1 + return acc +""" + c = get_contract(code) + # 1 + (2<<1) + (4<<2) + (8<<3) = 1 + 4 + 16 + 64 = 85 + assert c.order_sum() == 85 + + +def test_flag_iter_target_type_mismatch(assert_compile_failed, get_contract): + from vyper.exceptions import TypeMismatch + + code = """ +flag A: + X +flag B: + Y + +@pure +@external +def f() -> uint256: + s: uint256 = 0 + for p: B in A.__values__: + s += convert(p, uint256) + return s +""" + assert_compile_failed(lambda: get_contract(code), TypeMismatch) + + +def test_flag_iter_invalid_iterator(assert_compile_failed, get_contract): + from vyper.exceptions import InvalidType + + code = """ +flag P: + A + +@pure +@external +def f() -> uint256: + s: uint256 = 0 + for p: P in 5: + s += 1 + return s +""" + assert_compile_failed(lambda: get_contract(code), InvalidType) + + +def test_flag_iter_wrong_target_type(assert_compile_failed, get_contract): + from vyper.exceptions import TypeMismatch + + code = """ +flag P: + A + B + +@pure +@external +def f() -> uint256: + s: uint256 = 0 + for p: uint256 in P.__values__: + s += p # wrong type; loop var must be P + return s +""" + assert_compile_failed(lambda: get_contract(code), TypeMismatch) + + +def test_nested_flag_type_iteration(get_contract): + code = """ +flag A: + X + Y + Z + +flag B: + P + Q + +@pure +@external +def product_sum() -> uint256: + s: uint256 = 0 + for a: A in A.__values__: + for b: B in B.__values__: + s += convert(a, uint256) * convert(b, uint256) + return s +""" + c = get_contract(code) + # a in {1,2,4}, b in {1,2} => (1+2+4)*(1+2) = 7*3 = 21 + assert c.product_sum() == 21 diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index f829e39ad7..64969af339 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -216,6 +216,23 @@ def parse_Name(self): def parse_Attribute(self): typ = self.expr._metadata["type"] + # Flag.__values__: materialize a static array of all flag values in order + # Left side must be a flag type expression. + if ( + self.expr.attr == "__values__" + and is_type_t(self.expr.value._metadata["type"], FlagT) + ): + flag_t = self.expr.value._metadata["type"].typedef + # Build the list of constant IR nodes for each flag value + # using declaration order from `_flag_members`. + elements = [] + for name, idx in flag_t._flag_members.items(): + value = 2 ** idx + elements.append(IRnode.from_list(value, typ=flag_t)) + + arr_t = SArrayT(flag_t, len(elements)) + return IRnode.from_list(["multi"] + elements, typ=arr_t) + # check if we have a flag constant, e.g. # [lib1].MyFlag.FOO if isinstance(typ, FlagT) and is_type_t(self.expr.value._metadata["type"], FlagT): diff --git a/vyper/codegen/stmt.py b/vyper/codegen/stmt.py index ffda836373..829abab4fa 100644 --- a/vyper/codegen/stmt.py +++ b/vyper/codegen/stmt.py @@ -175,8 +175,7 @@ def parse_For(self): with self.context.block_scope(): if self.stmt.get("iter.func.id") == "range": return self._parse_For_range() - else: - return self._parse_For_list() + return self._parse_For_list() def _parse_For_range(self): assert "type" in self.stmt.target.target._metadata @@ -279,6 +278,8 @@ def _parse_For_list(self): del self.context.forvars[varname] return b1.resolve(IRnode.from_list(ret)) + + def parse_AugAssign(self): target = self._get_target(self.stmt.target) right = Expr.parse_value_expr(self.stmt.value, self.context) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 70d8cbdd67..36b78cb031 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -575,6 +575,7 @@ def visit_For(self, node): # sanity check the postcondition of analyse_range_iter assert isinstance(target_type, IntegerT) else: + # Iterate over lists/arrays (including Flag.__values__) # note: using `node.target` here results in bad source location. iter_var = self._analyse_list_iter(node.target.target, node.iter, target_type) diff --git a/vyper/semantics/types/user.py b/vyper/semantics/types/user.py index 0ee2e4e1ab..697d24b45c 100644 --- a/vyper/semantics/types/user.py +++ b/vyper/semantics/types/user.py @@ -21,7 +21,7 @@ ) from vyper.semantics.data_locations import DataLocation from vyper.semantics.types.base import VyperType -from vyper.semantics.types.subscriptable import HashMapT +from vyper.semantics.types.subscriptable import HashMapT, SArrayT from vyper.semantics.types.utils import type_from_abi, type_from_annotation from vyper.utils import keccak256 from vyper.warnings import Deprecation, vyper_warn @@ -75,6 +75,13 @@ def __init__(self, name: str, members: dict) -> None: self._helper._id = name def get_type_member(self, key: str, node: vy_ast.VyperNode) -> "VyperType": + # Special iterator helper for flags: `Flag.__values__` + # Returns a static array type of all flag values in declaration order. + if key == "__values__": + return SArrayT(self, len(self._flag_members)) + + # Regular flag member access (e.g., `Flag.FOO`) validates the member name + # and yields the flag type in expression position. self._helper.get_member(key, node) return self From 26eb76c437ae36e2ad2abb3f197c630890f88fa1 Mon Sep 17 00:00:00 2001 From: z80 Date: Sat, 30 Aug 2025 00:22:19 -0400 Subject: [PATCH 2/8] chore: remove agents.md from repo --- agents.md | 45 --------------------------------------------- 1 file changed, 45 deletions(-) delete mode 100644 agents.md diff --git a/agents.md b/agents.md deleted file mode 100644 index 6e5b2f3267..0000000000 --- a/agents.md +++ /dev/null @@ -1,45 +0,0 @@ -Vyper Dev Environment: Virtualenv + Tests - -Quick TL;DR -- Use the existing virtualenv: `. .venv/bin/activate` and run tests: `pytest` or `./quicktest.sh`. -- If creating fresh: `python3.12 -m venv .venv && . .venv/bin/activate && pip -U pip wheel setuptools && pip install -e .[test]`. - -Supported Python -- Project supports Python 3.10–3.13. Prefer 3.12 for parity with CI. - -System Prereqs (Linux) -- git (setuptools-scm reads the commit hash) -- build tools for any wheels that don’t have prebuilt binaries: `build-essential` (Debian/Ubuntu). Optional: `pkg-config`. -- If network-restricted in the CLI sandbox, request approval for network before running `pip install`. - -Set Up From Scratch -1) Create venv - - `python3.12 -m venv .venv` - - `source .venv/bin/activate` - - Upgrade basics: `pip install -U pip wheel setuptools` - -2) Install package + test deps - - `pip install -e .[test]` - Includes: pytest, xdist, instafail, split, hypothesis[lark], py-evm, eth-account, hexbytes, pyrevm, etc. - - If setuptools-scm missing (rare): `pip install setuptools_scm` - -3) Verify compiler import - - `python -c "import vyper; print(vyper.__version__)"` - - If you see `ModuleNotFoundError: No module named 'Crypto'`, install `pycryptodome` (part of install_requires): - `pip install pycryptodome` - -Run Tests -- Full suite: `pytest` (parallel by default via xdist) or `./quicktest.sh` (bails on first failure). -- Faster dev loop: `./quicktest.sh -m "not fuzzing" -n0` -- Single test file: `pytest tests/functional/codegen/features/test_flag_iteration_type.py -q` -- Single test: `pytest tests/.../test_flag_iteration_type.py::test_iterate_over_flag_type -q` - -Notes -- The repo writes commit hash into `vyper/vyper_git_commithash.txt` during editable install; ensure `git` is available. -- Some optional tests are marked `hevm`; they may require external tooling and are not run by default. -- If running inside the Codex CLI with network restrictions, most installs require network; ask for approval when needed. - -Known Pitfalls -- Using system Python (e.g., 3.13) without the venv can miss dependencies (e.g., `Crypto`). Always activate `.venv`. -- If pip tries to build wheels from source and fails, install system build tools and retry (`build-essential`, `pkg-config`). - From 2bee3dc384cb3b7481bb6bb999377d9cdf127ecf Mon Sep 17 00:00:00 2001 From: z80 Date: Tue, 2 Sep 2025 10:34:01 -0400 Subject: [PATCH 3/8] fix: resolve import cycle in user.py Move HashMapT and SArrayT imports to local scope to avoid circular dependency --- vyper/semantics/types/user.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/semantics/types/user.py b/vyper/semantics/types/user.py index 697d24b45c..2267d38d43 100644 --- a/vyper/semantics/types/user.py +++ b/vyper/semantics/types/user.py @@ -21,7 +21,7 @@ ) from vyper.semantics.data_locations import DataLocation from vyper.semantics.types.base import VyperType -from vyper.semantics.types.subscriptable import HashMapT, SArrayT +# Import moved to local scope to avoid circular dependency from vyper.semantics.types.utils import type_from_abi, type_from_annotation from vyper.utils import keccak256 from vyper.warnings import Deprecation, vyper_warn @@ -78,6 +78,7 @@ def get_type_member(self, key: str, node: vy_ast.VyperNode) -> "VyperType": # Special iterator helper for flags: `Flag.__values__` # Returns a static array type of all flag values in declaration order. if key == "__values__": + from vyper.semantics.types.subscriptable import SArrayT return SArrayT(self, len(self._flag_members)) # Regular flag member access (e.g., `Flag.FOO`) validates the member name @@ -452,6 +453,7 @@ def _ctor_call_return(self, node: vy_ast.Call) -> "StructT": raise VariableDeclarationException( "Struct values must be declared as kwargs e.g. Foo(a=1, b=2)", node.args[0] ) + from vyper.semantics.types.subscriptable import HashMapT if next((i for i in self.member_types.values() if isinstance(i, HashMapT)), False): raise VariableDeclarationException( "Struct contains a mapping and so cannot be declared as a literal", node From 3ca2abc4185c49f31135f07b78f03c353868abde Mon Sep 17 00:00:00 2001 From: z80 Date: Tue, 2 Sep 2025 21:05:17 -0400 Subject: [PATCH 4/8] fix: clean up unrelated change there was the movement of an import that we might want to do, but is unrelated to the changes introduced in this PR --- vyper/semantics/types/user.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vyper/semantics/types/user.py b/vyper/semantics/types/user.py index 2267d38d43..663ba265d4 100644 --- a/vyper/semantics/types/user.py +++ b/vyper/semantics/types/user.py @@ -21,7 +21,7 @@ ) from vyper.semantics.data_locations import DataLocation from vyper.semantics.types.base import VyperType -# Import moved to local scope to avoid circular dependency +from vyper.semantics.types.subscriptable import HashMapT from vyper.semantics.types.utils import type_from_abi, type_from_annotation from vyper.utils import keccak256 from vyper.warnings import Deprecation, vyper_warn @@ -453,7 +453,6 @@ def _ctor_call_return(self, node: vy_ast.Call) -> "StructT": raise VariableDeclarationException( "Struct values must be declared as kwargs e.g. Foo(a=1, b=2)", node.args[0] ) - from vyper.semantics.types.subscriptable import HashMapT if next((i for i in self.member_types.values() if isinstance(i, HashMapT)), False): raise VariableDeclarationException( "Struct contains a mapping and so cannot be declared as a literal", node From bc441298a4144bd748d9f836c3211268953e0e72 Mon Sep 17 00:00:00 2001 From: z80 Date: Tue, 2 Sep 2025 21:06:26 -0400 Subject: [PATCH 5/8] fix: remove unrelated formatting changes --- vyper/codegen/stmt.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vyper/codegen/stmt.py b/vyper/codegen/stmt.py index 829abab4fa..ffda836373 100644 --- a/vyper/codegen/stmt.py +++ b/vyper/codegen/stmt.py @@ -175,7 +175,8 @@ def parse_For(self): with self.context.block_scope(): if self.stmt.get("iter.func.id") == "range": return self._parse_For_range() - return self._parse_For_list() + else: + return self._parse_For_list() def _parse_For_range(self): assert "type" in self.stmt.target.target._metadata @@ -278,8 +279,6 @@ def _parse_For_list(self): del self.context.forvars[varname] return b1.resolve(IRnode.from_list(ret)) - - def parse_AugAssign(self): target = self._get_target(self.stmt.target) right = Expr.parse_value_expr(self.stmt.value, self.context) From 5b31a52584805a126bddcd1034698161b6d6e752 Mon Sep 17 00:00:00 2001 From: z80 Date: Wed, 3 Sep 2025 19:55:10 -0400 Subject: [PATCH 6/8] fix: lint errors --- vyper/codegen/expr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index 64969af339..f639438b01 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -226,8 +226,8 @@ def parse_Attribute(self): # Build the list of constant IR nodes for each flag value # using declaration order from `_flag_members`. elements = [] - for name, idx in flag_t._flag_members.items(): - value = 2 ** idx + for idx in flag_t._flag_members.values(): + value = 2**idx elements.append(IRnode.from_list(value, typ=flag_t)) arr_t = SArrayT(flag_t, len(elements)) From 262af816c101546a89686d32efe08d23b9130e4e Mon Sep 17 00:00:00 2001 From: z80 Date: Wed, 3 Sep 2025 20:05:39 -0400 Subject: [PATCH 7/8] fix: properly fix lint --- vyper/codegen/expr.py | 12 +++++------- vyper/semantics/types/user.py | 1 + 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index f639438b01..216624f244 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -218,10 +218,7 @@ def parse_Attribute(self): # Flag.__values__: materialize a static array of all flag values in order # Left side must be a flag type expression. - if ( - self.expr.attr == "__values__" - and is_type_t(self.expr.value._metadata["type"], FlagT) - ): + if self.expr.attr == "__values__" and is_type_t(self.expr.value._metadata["type"], FlagT): flag_t = self.expr.value._metadata["type"].typedef # Build the list of constant IR nodes for each flag value # using declaration order from `_flag_members`. @@ -493,9 +490,10 @@ def build_in_comparator(self): ret = ["seq"] - with left.cache_when_complex("needle") as (b1, left), right.cache_when_complex( - "haystack" - ) as (b2, right): + with ( + left.cache_when_complex("needle") as (b1, left), + right.cache_when_complex("haystack") as (b2, right), + ): # unroll the loop for compile-time list literals if right.value == "multi": # empty list literals should be rejected at typechecking time diff --git a/vyper/semantics/types/user.py b/vyper/semantics/types/user.py index 663ba265d4..4948abc474 100644 --- a/vyper/semantics/types/user.py +++ b/vyper/semantics/types/user.py @@ -79,6 +79,7 @@ def get_type_member(self, key: str, node: vy_ast.VyperNode) -> "VyperType": # Returns a static array type of all flag values in declaration order. if key == "__values__": from vyper.semantics.types.subscriptable import SArrayT + return SArrayT(self, len(self._flag_members)) # Regular flag member access (e.g., `Flag.FOO`) validates the member name From 77fd5979c41b008f1e818783bbeb746a0d3e0ee7 Mon Sep 17 00:00:00 2001 From: z80 Date: Fri, 26 Sep 2025 15:04:07 -0400 Subject: [PATCH 8/8] fix: keep flag member lookups type-only --- .../features/test_flag_iteration_type.py | 16 +++++++++ vyper/semantics/types/user.py | 35 +++++++++++-------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/tests/functional/codegen/features/test_flag_iteration_type.py b/tests/functional/codegen/features/test_flag_iteration_type.py index 0f04d5438a..6d95683db3 100644 --- a/tests/functional/codegen/features/test_flag_iteration_type.py +++ b/tests/functional/codegen/features/test_flag_iteration_type.py @@ -118,6 +118,22 @@ def f() -> uint256: assert_compile_failed(lambda: get_contract(code), TypeMismatch) +def test_flag_instance_member_access(assert_compile_failed, get_contract): + from vyper.exceptions import UnknownAttribute + + code = """ +flag P: + A + B + +@pure +@external +def f(p: P) -> uint256: + return convert(p.A, uint256) +""" + assert_compile_failed(lambda: get_contract(code), UnknownAttribute) + + def test_nested_flag_type_iteration(get_contract): code = """ flag A: diff --git a/vyper/semantics/types/user.py b/vyper/semantics/types/user.py index 4948abc474..71a2e380f1 100644 --- a/vyper/semantics/types/user.py +++ b/vyper/semantics/types/user.py @@ -11,6 +11,7 @@ NamespaceCollision, StructureException, UnfoldableNode, + UnknownAttribute, VariableDeclarationException, ) from vyper.semantics.analysis.base import Modifiability @@ -65,27 +66,27 @@ def __init__(self, name: str, members: dict) -> None: self._id = name - self._flag_members = members + self._flag_members = dict(members) - # use a VyperType for convenient access to the `get_member` function - # also conveniently checks well-formedness of the members namespace - self._helper = VyperType(members) + for member_name in self._flag_members: + self.add_member(member_name, self) - # set the name for exception handling in `get_member` - self._helper._id = name + from vyper.semantics.types.subscriptable import SArrayT + + self.add_member("__values__", SArrayT(self, len(self._flag_members))) def get_type_member(self, key: str, node: vy_ast.VyperNode) -> "VyperType": - # Special iterator helper for flags: `Flag.__values__` - # Returns a static array type of all flag values in declaration order. - if key == "__values__": - from vyper.semantics.types.subscriptable import SArrayT + return super().get_member(key, node) - return SArrayT(self, len(self._flag_members)) + def get_member(self, key: str, node: vy_ast.VyperNode) -> "VyperType": + if key in self.members: + msg = ( + f"{self} members are available on the type. " + f"Use {self.name}.{key} instead." + ) + raise UnknownAttribute(msg, node) - # Regular flag member access (e.g., `Flag.FOO`) validates the member name - # and yields the flag type in expression position. - self._helper.get_member(key, node) - return self + return super().get_member(key, node) def __str__(self): return f"{self.name}" @@ -144,6 +145,10 @@ def from_FlagDef(cls, base_node: vy_ast.FlagDef) -> "FlagT": raise FlagDeclarationException("Invalid syntax for flag member", node) member_name = node.value.id + if member_name == "__values__": + raise FlagDeclarationException( + "Flag member '__values__' is reserved", node.value + ) if member_name in members: raise FlagDeclarationException( f"Flag member '{member_name}' has already been declared", node.value