From c923714d9c09b1ba881c8f13406f44b85d93d0e7 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 22 Nov 2023 17:26:08 -0800 Subject: [PATCH] wip: .vyi files --- examples/tokens/ERC721.vy | 2 +- vyper/ast/annotation.py | 2 + vyper/ast/nodes.py | 12 +- vyper/ast/nodes.pyi | 6 +- vyper/semantics/analysis/module.py | 16 +- vyper/semantics/types/function.py | 409 ++++++++++++++++++----------- vyper/semantics/types/module.py | 19 +- vyper/semantics/types/user.py | 93 +++---- 8 files changed, 337 insertions(+), 222 deletions(-) diff --git a/examples/tokens/ERC721.vy b/examples/tokens/ERC721.vy index 5125040399..d5e6cfc35c 100644 --- a/examples/tokens/ERC721.vy +++ b/examples/tokens/ERC721.vy @@ -14,7 +14,7 @@ interface ERC721Receiver: _operator: address, _from: address, _tokenId: uint256, - _data: Bytes[1024] + _data: Bytes[...] ) -> bytes4: nonpayable diff --git a/vyper/ast/annotation.py b/vyper/ast/annotation.py index dad164b1bb..8eb7ca3abb 100644 --- a/vyper/ast/annotation.py +++ b/vyper/ast/annotation.py @@ -164,6 +164,8 @@ def visit_Constant(self, node): node.ast_type = "Str" elif isinstance(node.value, bytes): node.ast_type = "Bytes" + elif isinstance(node.value, Ellipsis.__class__): + node.ast_type = "Ellipsis" else: raise SyntaxException( "Invalid syntax (unsupported Python Constant AST node).", diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index 604ccd36e9..f9c45c1034 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -898,12 +898,16 @@ def validate(self): raise InvalidLiteral("Cannot have an empty tuple", self) -class Dict(ExprNode): - __slots__ = ("keys", "values") +class NameConstant(Constant): + __slots__ = () -class NameConstant(Constant): - __slots__ = ("value",) +class Ellipsis(Constant): + __slots__ = () + + +class Dict(ExprNode): + __slots__ = ("keys", "values") class Name(ExprNode): diff --git a/vyper/ast/nodes.pyi b/vyper/ast/nodes.pyi index 47c9af8526..569981b6ef 100644 --- a/vyper/ast/nodes.pyi +++ b/vyper/ast/nodes.pyi @@ -121,6 +121,10 @@ class Bytes(Constant): @property def s(self): ... +class NameConstant(Constant): ... + +class Ellipsis(Constant): ... + class List(VyperNode): elements: list = ... @@ -131,8 +135,6 @@ class Dict(VyperNode): keys: list = ... values: list = ... -class NameConstant(Constant): ... - class Name(VyperNode): id: str = ... _type: str = ... diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index 513ef6ac83..0b985b0999 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -378,12 +378,22 @@ def _load_import(self, node: vy_ast.VyperNode, level: int, module_str: str, alia except FileNotFoundError: pass + try: + file = self.input_bundle.load_file(path.with_suffix(".vyi")) + assert isinstance(file, FileInput) # mypy hint + interface_ast = vy_ast.parse_to_ast(file.source_code, contract_name=str(file.path)) + return InterfaceT.from_vyi(interface_ast) + except FileNotFoundError: + pass + try: file = self.input_bundle.load_file(path.with_suffix(".json")) assert isinstance(file, ABIInput) # mypy hint return InterfaceT.from_json_abi(str(file.path), file.abi) except FileNotFoundError: - raise ModuleNotFoundError(module_str) + pass + + raise ModuleNotFoundError(module_str) # convert an import to a path (without suffix) @@ -425,7 +435,7 @@ def _load_builtin_import(level: int, module_str: str) -> InterfaceT: remapped_module = remapped_module.removeprefix("vyper.interfaces") remapped_module = vyper.builtins.interfaces.__package__ + remapped_module - path = _import_to_path(level, remapped_module).with_suffix(".vy") + path = _import_to_path(level, remapped_module).with_suffix(".vyi") try: file = input_bundle.load_file(path) @@ -435,4 +445,4 @@ def _load_builtin_import(level: int, module_str: str) -> InterfaceT: # TODO: it might be good to cache this computation interface_ast = vy_ast.parse_to_ast(file.source_code, module_path=path) - return InterfaceT.from_Module(interface_ast, name=module_str) + return InterfaceT.from_vyi(interface_ast) diff --git a/vyper/semantics/types/function.py b/vyper/semantics/types/function.py index e39c77706f..adf1545638 100644 --- a/vyper/semantics/types/function.py +++ b/vyper/semantics/types/function.py @@ -165,190 +165,172 @@ def from_abi(cls, abi: Dict) -> "ContractFunctionT": ) @classmethod - def from_FunctionDef( - cls, node: vy_ast.FunctionDef, is_interface: Optional[bool] = False - ) -> "ContractFunctionT": + def from_InterfaceDef(cls, funcdef: vy_ast.FunctionDef) -> "ContractFunctionT": """ - Generate a `ContractFunctionT` object from a `FunctionDef` node. + Generate a `ContractFunctionT` object from a `FunctionDef` inside + of an `InterfaceDef` Arguments --------- - node : FunctionDef + funcdef: FunctionDef Vyper ast node to generate the function definition from. - is_interface: bool, optional - Boolean indicating if the function definition is part of an interface. Returns ------- ContractFunctionT """ - kwargs: Dict[str, Any] = {} - if is_interface: - # FunctionDef with stateMutability in body (Interface defintions) - if ( - len(node.body) == 1 - and isinstance(node.body[0], vy_ast.Expr) - and isinstance(node.body[0].value, vy_ast.Name) - and StateMutability.is_valid_value(node.body[0].value.id) - ): - # Interfaces are always public - kwargs["function_visibility"] = FunctionVisibility.EXTERNAL - kwargs["state_mutability"] = StateMutability(node.body[0].value.id) - elif len(node.body) == 1 and node.body[0].get("value.id") in ("constant", "modifying"): - if node.body[0].value.id == "constant": - expected = "view or pure" - else: - expected = "payable or nonpayable" - raise StructureException( - f"State mutability should be set to {expected}", node.body[0] - ) + # FunctionDef with stateMutability in body (Interface defintions) + body = funcdef.body + if ( + len(body) == 1 + and isinstance(body[0], vy_ast.Expr) + and isinstance(body[0].value, vy_ast.Name) + and StateMutability.is_valid_value(body[0].value.id) + ): + # Interfaces are always public + function_visibility = FunctionVisibility.EXTERNAL + state_mutability = StateMutability(body[0].value.id) + # handle errors + elif len(body) == 1 and body[0].get("value.id") in ("constant", "modifying"): + if body[0].value.id == "constant": + expected = "view or pure" else: - raise StructureException( - "Body must only contain state mutability label", node.body[0] - ) - + expected = "payable or nonpayable" + raise StructureException(f"State mutability should be set to {expected}", body[0]) else: - # FunctionDef with decorators (normal functions) - for decorator in node.decorator_list: - if isinstance(decorator, vy_ast.Call): - if "nonreentrant" in kwargs: - raise StructureException( - "nonreentrant decorator is already set with key: " - f"{kwargs['nonreentrant']}", - node, - ) + raise StructureException("Body must only contain state mutability label", body[0]) - if decorator.get("func.id") != "nonreentrant": - raise StructureException("Decorator is not callable", decorator) - if len(decorator.args) != 1 or not isinstance(decorator.args[0], vy_ast.Str): - raise StructureException( - "@nonreentrant name must be given as a single string literal", decorator - ) + if funcdef.name == "__init__": + raise FunctionDeclarationException("Constructors cannot appear in interfaces", funcdef) - if node.name == "__init__": - msg = "Nonreentrant decorator disallowed on `__init__`" - raise FunctionDeclarationException(msg, decorator) - - nonreentrant_key = decorator.args[0].value - validate_identifier(nonreentrant_key, decorator.args[0]) - - kwargs["nonreentrant"] = nonreentrant_key - - elif isinstance(decorator, vy_ast.Name): - if FunctionVisibility.is_valid_value(decorator.id): - if "function_visibility" in kwargs: - raise FunctionDeclarationException( - f"Visibility is already set to: {kwargs['function_visibility']}", - node, - ) - kwargs["function_visibility"] = FunctionVisibility(decorator.id) - - elif StateMutability.is_valid_value(decorator.id): - if "state_mutability" in kwargs: - raise FunctionDeclarationException( - f"Mutability is already set to: {kwargs['state_mutability']}", node - ) - kwargs["state_mutability"] = StateMutability(decorator.id) - - else: - if decorator.id == "constant": - warnings.warn( - "'@constant' decorator has been removed (see VIP2040). " - "Use `@view` instead.", - DeprecationWarning, - ) - raise FunctionDeclarationException( - f"Unknown decorator: {decorator.id}", decorator - ) + if funcdef.name == "__default__": + raise FunctionDeclarationException( + "Default functions cannot appear in interfaces", funcdef + ) + + positional_args, keyword_args = _parse_args(funcdef) + + return_type = _parse_return_type(funcdef) + + return cls( + funcdef.name, + positional_args, + keyword_args, + return_type, + function_visibility, + state_mutability, + nonreentrant_key=None, + ast_def=funcdef, + ) + + @classmethod + def from_vyi(cls, funcdef: vy_ast.FunctionDef) -> "ContractFunctionT": + """ + Generate a `ContractFunctionT` object from a `FunctionDef` inside + of an interface (`.vyi`) file + + Arguments + --------- + funcdef: FunctionDef + Vyper ast node to generate the function definition from. + + Returns + ------- + ContractFunctionT + """ + function_visibility, state_mutability, nonreentrant_key = _parse_decorators(funcdef) + + if nonreentrant_key is not None: + raise FunctionDeclarationException( + "nonreentrant key not allowed in interfaces", funcdef + ) - else: - raise StructureException("Bad decorator syntax", decorator) + if funcdef.name == "__init__": + raise FunctionDeclarationException("Constructors cannot appear in interfaces", funcdef) - if "function_visibility" not in kwargs: + if funcdef.name == "__default__": raise FunctionDeclarationException( - f"Visibility must be set to one of: {', '.join(FunctionVisibility.values())}", node + "Default functions cannot appear in interfaces", funcdef ) - if node.name == "__default__": - if kwargs["function_visibility"] != FunctionVisibility.EXTERNAL: + positional_args, keyword_args = _parse_args(funcdef) + + return_type = _parse_return_type(funcdef) + + if len(funcdef.body) != 1 or not isinstance(funcdef.body[0].get("value"), vy_ast.Ellipsis): + raise FunctionDeclarationException("function body in an interface can only be ...!", funcdef) + + return cls( + funcdef.name, + positional_args, + keyword_args, + return_type, + function_visibility, + state_mutability, + nonreentrant_key, + ast_def=funcdef, + ) + + @classmethod + def from_FunctionDef(cls, funcdef: vy_ast.FunctionDef) -> "ContractFunctionT": + """ + Generate a `ContractFunctionT` object from a `FunctionDef` node. + + Arguments + --------- + funcdef: FunctionDef + Vyper ast node to generate the function definition from. + + Returns + ------- + ContractFunctionT + """ + function_visibility, state_mutability, nonreentrant_key = _parse_decorators(funcdef) + + positional_args, keyword_args = _parse_args(funcdef) + + return_type = _parse_return_type(funcdef) + + # validate default and init functions + if funcdef.name == "__default__": + if function_visibility != FunctionVisibility.EXTERNAL: raise FunctionDeclarationException( - "Default function must be marked as `@external`", node + "Default function must be marked as `@external`", funcdef ) - if node.args.args: + if funcdef.args.args: raise FunctionDeclarationException( - "Default function may not receive any arguments", node.args.args[0] + "Default function may not receive any arguments", funcdef.args.args[0] ) - if "state_mutability" not in kwargs: - # Assume nonpayable if not set at all (cannot accept Ether, but can modify state) - kwargs["state_mutability"] = StateMutability.NONPAYABLE - - if kwargs["state_mutability"] == StateMutability.PURE and "nonreentrant" in kwargs: - raise StructureException("Cannot use reentrancy guard on pure functions", node) - - if node.name == "__init__": + if funcdef.name == "__init__": if ( - kwargs["state_mutability"] in (StateMutability.PURE, StateMutability.VIEW) - or kwargs["function_visibility"] == FunctionVisibility.INTERNAL + state_mutability in (StateMutability.PURE, StateMutability.VIEW) + or function_visibility == FunctionVisibility.INTERNAL ): raise FunctionDeclarationException( - "Constructor cannot be marked as `@pure`, `@view` or `@internal`", node + "Constructor cannot be marked as `@pure`, `@view` or `@internal`", funcdef ) - - # call arguments - if node.args.defaults: + if return_type is not None: raise FunctionDeclarationException( - "Constructor may not use default arguments", node.args.defaults[0] + "Constructor may not have a return type", funcdef.returns ) - argnames = set() # for checking uniqueness - n_total_args = len(node.args.args) - n_positional_args = n_total_args - len(node.args.defaults) - - positional_args: list[PositionalArg] = [] - keyword_args: list[KeywordArg] = [] - - for i, arg in enumerate(node.args.args): - argname = arg.arg - if argname in ("gas", "value", "skip_contract_check", "default_return_value"): - raise ArgumentException( - f"Cannot use '{argname}' as a variable name in a function input", arg + # call arguments + if funcdef.args.defaults: + raise FunctionDeclarationException( + "Constructor may not use default arguments", funcdef.args.defaults[0] ) - if argname in argnames: - raise ArgumentException(f"Function contains multiple inputs named {argname}", arg) - - if arg.annotation is None: - raise ArgumentException(f"Function argument '{argname}' is missing a type", arg) - - type_ = type_from_annotation(arg.annotation, DataLocation.CALLDATA) - - if i < n_positional_args: - positional_args.append(PositionalArg(argname, type_, ast_source=arg)) - else: - value = node.args.defaults[i - n_positional_args] - if not check_kwargable(value): - raise StateAccessViolation( - "Value must be literal or environment variable", value - ) - validate_expected_type(value, type_) - keyword_args.append(KeywordArg(argname, type_, value, ast_source=arg)) - - argnames.add(argname) - - # return types - if node.returns is None: - return_type = None - elif node.name == "__init__": - raise FunctionDeclarationException( - "Constructor may not have a return type", node.returns - ) - elif isinstance(node.returns, (vy_ast.Name, vy_ast.Subscript, vy_ast.Tuple)): - # note: consider, for cleanliness, adding DataLocation.RETURN_VALUE - return_type = type_from_annotation(node.returns, DataLocation.MEMORY) - else: - raise InvalidType("Function return value must be a type name or tuple", node.returns) - return cls(node.name, positional_args, keyword_args, return_type, ast_def=node, **kwargs) + return cls( + funcdef.name, + positional_args, + keyword_args, + return_type, + function_visibility, + state_mutability, + nonreentrant_key, + ast_def=funcdef, + ) def set_reentrancy_key_position(self, position: StorageSlot) -> None: if hasattr(self, "reentrancy_key_position"): @@ -592,6 +574,131 @@ def abi_signature_for_kwargs(self, kwargs: list[KeywordArg]) -> str: return self.name + "(" + ",".join([arg.typ.abi_type.selector_name() for arg in args]) + ")" +def _parse_return_type(funcdef: vy_ast.FunctionDef) -> Optional[VyperType]: + # return types + if funcdef.returns is None: + return None + if isinstance(funcdef.returns, (vy_ast.Name, vy_ast.Subscript, vy_ast.Tuple)): + # note: consider, for cleanliness, adding DataLocation.RETURN_VALUE + return type_from_annotation(funcdef.returns, DataLocation.MEMORY) + + raise InvalidType("Function return value must be a type name or tuple", funcdef.returns) + + +def _parse_decorators( + funcdef: vy_ast.FunctionDef, +) -> tuple[FunctionVisibility, StateMutability, Optional[str]]: + function_visibility = None + state_mutability = None + nonreentrant_key = None + + for decorator in funcdef.decorator_list: + if isinstance(decorator, vy_ast.Call): + if nonreentrant_key is not None: + raise StructureException( + "nonreentrant decorator is already set with key: " f"{nonreentrant_key}", + funcdef, + ) + + if decorator.get("func.id") != "nonreentrant": + raise StructureException("Decorator is not callable", decorator) + if len(decorator.args) != 1 or not isinstance(decorator.args[0], vy_ast.Str): + raise StructureException( + "@nonreentrant name must be given as a single string literal", decorator + ) + + if funcdef.name == "__init__": + msg = "Nonreentrant decorator disallowed on `__init__`" + raise FunctionDeclarationException(msg, decorator) + + nonreentrant_key = decorator.args[0].value + validate_identifier(nonreentrant_key, decorator.args[0]) + + elif isinstance(decorator, vy_ast.Name): + if FunctionVisibility.is_valid_value(decorator.id): + if function_visibility is not None: + raise FunctionDeclarationException( + f"Visibility is already set to: {function_visibility}", funcdef + ) + function_visibility = FunctionVisibility(decorator.id) + + elif StateMutability.is_valid_value(decorator.id): + if state_mutability is not None: + raise FunctionDeclarationException( + f"Mutability is already set to: {state_mutability}", funcdef + ) + state_mutability = StateMutability(decorator.id) + + else: + if decorator.id == "constant": + warnings.warn( + "'@constant' decorator has been removed (see VIP2040). " + "Use `@view` instead.", + DeprecationWarning, + ) + raise FunctionDeclarationException(f"Unknown decorator: {decorator.id}", decorator) + + else: + raise StructureException("Bad decorator syntax", decorator) + + if function_visibility is None: + raise FunctionDeclarationException( + f"Visibility must be set to one of: {', '.join(FunctionVisibility.values())}", funcdef + ) + + if state_mutability is None: + # default to nonpayable + state_mutability = StateMutability.NONPAYABLE + + if state_mutability == StateMutability.PURE and nonreentrant_key is not None: + raise StructureException("Cannot use reentrancy guard on pure functions", funcdef) + + # assert function_visibility is not None # mypy + # assert state_mutability is not None # mypy + return function_visibility, state_mutability, nonreentrant_key + + +def _parse_args( + funcdef: vy_ast.FunctionDef, is_interface: bool = False +) -> tuple[list[PositionalArg], list[KeywordArg]]: + argnames = set() # for checking uniqueness + n_total_args = len(funcdef.args.args) + n_positional_args = n_total_args - len(funcdef.args.defaults) + + # if is_interface and len(funcdef.args.defaults) != 0: + # raise FunctionDeclarationException("function interfaces cannot have kwargs!") + + positional_args = [] + keyword_args = [] + + for i, arg in enumerate(funcdef.args.args): + argname = arg.arg + if argname in ("gas", "value", "skip_contract_check", "default_return_value"): + raise ArgumentException( + f"Cannot use '{argname}' as a variable name in a function input", arg + ) + if argname in argnames: + raise ArgumentException(f"Function contains multiple inputs named {argname}", arg) + + if arg.annotation is None: + raise ArgumentException(f"Function argument '{argname}' is missing a type", arg) + + type_ = type_from_annotation(arg.annotation, DataLocation.CALLDATA) + + if i < n_positional_args: + positional_args.append(PositionalArg(argname, type_, ast_source=arg)) + else: + value = funcdef.args.defaults[i - n_positional_args] + if not check_kwargable(value): + raise StateAccessViolation("Value must be literal or environment variable", value) + validate_expected_type(value, type_) + keyword_args.append(KeywordArg(argname, type_, value, ast_source=arg)) + + argnames.add(argname) + + return positional_args, keyword_args + + class MemberFunctionT(VyperType): """ Member function type definition. diff --git a/vyper/semantics/types/module.py b/vyper/semantics/types/module.py index 264ff40e09..93c1d93c49 100644 --- a/vyper/semantics/types/module.py +++ b/vyper/semantics/types/module.py @@ -3,28 +3,25 @@ from vyper import ast as vy_ast from vyper.semantics.types.base import VyperType -from vyper.semantics.types.function import MemberFunctionT -from vyper.semantics.types.primitives import AddressT from vyper.semantics.types.user import InterfaceT # Datatype to store all module information. class ModuleT(VyperType): def __init__(self, module: vy_ast.Module, name: Optional[str] = None): + super().__init__() + self._module = module self._id = name or module.path # compute the interface, note this has the side effect of checking # for function collisions - interface_t = self.interface - - members = {} + _ = self.interface for f in self.functions: - members[f.name] = f._metadata["type"] - - super().__init__(members) + # note: this checks for collisions + self.add_member(f.name, f._metadata["type"]) def get_type_member(self, key: str, node: vy_ast.VyperNode) -> "VyperType": return self._helper.get_member(key, node) @@ -42,6 +39,10 @@ def _ctor_arg_types(self, node): def functions(self): return self._module.get_children(vy_ast.FunctionDef) + @property + def events(self): + return self._module.get_children(vy_ast.EventDef) + @cached_property def variables(self): # variables that this module defines, ex. @@ -59,4 +60,4 @@ def immutable_section_bytes(self): @cached_property def interface(self): - return InterfaceT.from_Module(self._module, name=self._id) + return InterfaceT.from_ModuleT(self) diff --git a/vyper/semantics/types/user.py b/vyper/semantics/types/user.py index 7f683bd864..e91b129145 100644 --- a/vyper/semantics/types/user.py +++ b/vyper/semantics/types/user.py @@ -1,5 +1,5 @@ from functools import cached_property -from typing import Optional +from typing import Any, Optional from vyper import ast as vy_ast from vyper.abi_types import ABI_Address, ABI_GIntM, ABI_Tuple, ABIType @@ -412,73 +412,62 @@ def from_json_abi(cls, name: str, abi: dict) -> "InterfaceT": return cls(name, members, events) @classmethod - def from_Module(cls, node: vy_ast.Module, name: Optional[str] = None) -> "InterfaceT": + def from_vyi(cls, module: vy_ast.Module) -> tuple[dict, dict]: + functions: dict = {} + events: dict = {} + for funcdef in module.get_children(vy_ast.FunctionDef): + func_t = ContractFunctionT.from_vyi(funcdef) + functions[funcdef.name] = func_t + + for eventdef in module.get_children(vy_ast.EventDef): + name = eventdef.name + if name in functions or name in events: + raise NamespaceCollision( + f"Interface contains multiple objects named '{name}'", module + ) + events[name] = EventT.from_EventDef(eventdef) + + return cls(name, functions, events) + + @classmethod + # node is `Any` here to avoid an import cycle + def from_ModuleT(cls, module_t: Any) -> "InterfaceT": """ Generate an `InterfaceT` object from a Vyper ast node. Arguments --------- - node : Module - Vyper ast node defining the interface + module_t: ModuleT + Vyper module type Returns ------- InterfaceT primitive interface type """ - members, events = _get_module_definitions(node) + funcs = {node.name: node._metadata["type"] for node in module_t.functions} + events = {node.name: node._metadata["type"] for node in module_t.events} - return cls(name, members, events) + return cls(module_t._id, funcs, events) @classmethod def from_InterfaceDef(cls, node: vy_ast.InterfaceDef) -> "InterfaceT": - members = _get_class_functions(node) - events = {} - - return cls(node.name, members, events) - - -# TODO: refactor this to use ModuleT information -def _get_module_definitions(base_node: vy_ast.Module) -> tuple[dict, dict]: - functions: dict = {} - events: dict = {} - for node in base_node.get_children(vy_ast.FunctionDef): - if "external" in [i.id for i in node.decorator_list if isinstance(i, vy_ast.Name)]: - func = ContractFunctionT.from_FunctionDef(node) - functions[node.name] = func - for node in base_node.get_children(vy_ast.VariableDecl, {"is_public": True}): - name = node.target.id - if name in functions: - raise NamespaceCollision( - f"Interface contains multiple functions named '{name}'", base_node - ) - functions[name] = ContractFunctionT.getter_from_VariableDecl(node) - for node in base_node.get_children(vy_ast.EventDef): - name = node.name - if name in functions or name in events: - raise NamespaceCollision( - f"Interface contains multiple objects named '{name}'", base_node - ) - events[name] = EventT.from_EventDef(node) - - return functions, events - + functions = {} + for node in node.body: + if not isinstance(node, vy_ast.FunctionDef): + raise StructureException("Interfaces can only contain function definitions", node) + if node.name in functions: + raise NamespaceCollision( + f"Interface contains multiple functions named '{node.name}'", node + ) + if len(node.decorator_list) > 0: + raise StructureException( + "Function definition in interface cannot be decorated", node.decorator_list[0] + ) + functions[node.name] = ContractFunctionT.from_FunctionDef(node, is_interface=True) -def _get_class_functions(base_node: vy_ast.InterfaceDef) -> dict[str, ContractFunctionT]: - functions = {} - for node in base_node.body: - if not isinstance(node, vy_ast.FunctionDef): - raise StructureException("Interfaces can only contain function definitions", node) - if node.name in functions: - raise NamespaceCollision( - f"Interface contains multiple functions named '{node.name}'", node - ) - if len(node.decorator_list) > 0: - raise StructureException( - "Function definition in interface cannot be decorated", node.decorator_list[0] - ) - functions[node.name] = ContractFunctionT.from_FunctionDef(node, is_interface=True) + events = {} - return functions + return cls(node.name, functions, events) class StructT(_UserType):