diff --git a/tests/parser/exceptions/test_structure_exception.py b/tests/parser/exceptions/test_structure_exception.py index f99eac45e1..08794b75f2 100644 --- a/tests/parser/exceptions/test_structure_exception.py +++ b/tests/parser/exceptions/test_structure_exception.py @@ -62,14 +62,6 @@ def double_nonreentrant(): pass """, """ -CALLDATACOPY: int128 - """, - """ -@external -def foo(): - BALANCE: int128 = 45 - """, - """ @external def foo(): true: int128 = 3 diff --git a/tests/parser/syntax/test_address_code.py b/tests/parser/syntax/test_address_code.py index c8a153a855..25fe1be0b4 100644 --- a/tests/parser/syntax/test_address_code.py +++ b/tests/parser/syntax/test_address_code.py @@ -5,7 +5,7 @@ from web3 import Web3 from vyper import compiler -from vyper.exceptions import StructureException, VyperException +from vyper.exceptions import NamespaceCollision, StructureException, VyperException # For reproducibility, use precompiled data of `hello: public(uint256)` using vyper 0.3.1 PRECOMPILED_ABI = """[{"stateMutability": "view", "type": "function", "name": "hello", "inputs": [], "outputs": [{"name": "", "type": "uint256"}], "gas": 2460}]""" # noqa: E501 @@ -114,17 +114,8 @@ def code_slice(x: address, y: uint256) -> Bytes[4]: """ code: public(Bytes[4]) """, - StructureException, - "'code' is a reserved keyword", - ), - ( - # User defined struct with `code` attribute - """ -struct S: - code: Bytes[4] -""", - StructureException, - "'code' is a reserved keyword", + NamespaceCollision, + "Value 'code' has already been declared", ), ], ) diff --git a/tests/parser/syntax/test_interfaces.py b/tests/parser/syntax/test_interfaces.py index d534393c11..c0afec5504 100644 --- a/tests/parser/syntax/test_interfaces.py +++ b/tests/parser/syntax/test_interfaces.py @@ -211,6 +211,24 @@ def kick(): payable kickers: HashMap[address, MyInterface] """, """ +interface Foo: + def append(a: uint256): payable + +@external +def bar(x: address): + a: Foo = Foo(x) + a.append(1) + """, + """ +interface Foo: + def pop(): payable + +@external +def foo(x: address): + a: Foo = Foo(x) + a.pop() + """, + """ interface ITestInterface: def foo() -> uint256: view diff --git a/tests/parser/syntax/test_structs.py b/tests/parser/syntax/test_structs.py index b89446e450..757c46c4b3 100644 --- a/tests/parser/syntax/test_structs.py +++ b/tests/parser/syntax/test_structs.py @@ -3,7 +3,6 @@ from vyper import compiler from vyper.exceptions import ( InvalidType, - NamespaceCollision, StructureException, TypeMismatch, UnknownAttribute, @@ -421,22 +420,6 @@ def foo(): ), ( """ -struct X: - bar: int128 - decimal: int128 - """, - NamespaceCollision, - ), - ( - """ -struct B: - num: int128 - address: address - """, - NamespaceCollision, - ), - ( - """ struct Foo: a: uint256 diff --git a/tests/parser/syntax/utils/test_function_names.py b/tests/parser/syntax/utils/test_function_names.py index ca7158a9b3..90e185558c 100644 --- a/tests/parser/syntax/utils/test_function_names.py +++ b/tests/parser/syntax/utils/test_function_names.py @@ -4,7 +4,7 @@ from vyper import compiler from vyper.exceptions import NamespaceCollision, StructureException -fail_list = [ # noqa: E122 +fail_list = [ """ @external def ő1qwerty(i: int128) -> int128: @@ -13,13 +13,7 @@ def ő1qwerty(i: int128) -> int128: """, """ @external -def int128(i: int128) -> int128: - temp_var : int128 = i - return temp_var - """, - """ -@external -def decimal(i: int128) -> int128: +def false(i: int128) -> int128: temp_var : int128 = i return temp_var """, @@ -27,13 +21,7 @@ def decimal(i: int128) -> int128: @external def wei(i: int128) -> int128: temp_var : int128 = i - return temp_var - """, - """ -@external -def false(i: int128) -> int128: - temp_var : int128 = i - return temp_var + return temp_var1 """, ] @@ -63,6 +51,32 @@ def first1(i: int128) -> int128: _var123 : int128 = i return _var123 """, + """ +@external +def int128(i: int128) -> int128: + temp_var : int128 = i + return temp_var + """, + """ +@external +def decimal(i: int128) -> int128: + temp_var : int128 = i + return temp_var + """, + """ +@external +def floor(): + pass + """, + """ +@internal +def append(): + pass + +@external +def foo(): + self.append() + """, ] diff --git a/tests/parser/types/test_identifier_naming.py b/tests/parser/types/test_identifier_naming.py index 8e4e2a2d66..d0daa6dc05 100755 --- a/tests/parser/types/test_identifier_naming.py +++ b/tests/parser/types/test_identifier_naming.py @@ -5,13 +5,11 @@ from vyper.codegen.expr import ENVIRONMENT_VARIABLES from vyper.exceptions import NamespaceCollision, StructureException, SyntaxException from vyper.semantics.namespace import RESERVED_KEYWORDS -from vyper.utils import FUNCTION_WHITELIST +from vyper.semantics.types.primitives import AddressT +BUILTIN_CONSTANTS = set(BUILTIN_CONSTANTS.keys()) ALL_RESERVED_KEYWORDS = ( - set(BUILTIN_CONSTANTS.keys()) - .union(BUILTIN_FUNCTIONS) - .union(RESERVED_KEYWORDS) - .union(ENVIRONMENT_VARIABLES) + BUILTIN_CONSTANTS | BUILTIN_FUNCTIONS | RESERVED_KEYWORDS | ENVIRONMENT_VARIABLES ) @@ -47,11 +45,27 @@ def test({constant}: int128): ) -RESERVED_KEYWORDS_NOT_WHITELISTED = sorted(ALL_RESERVED_KEYWORDS.difference(FUNCTION_WHITELIST)) +PYTHON_KEYWORDS = {"if", "for", "while", "pass", "def", "assert", "continue", "raise"} + +SELF_NAMESPACE_MEMBERS = set(AddressT._type_members.keys()) +DISALLOWED_FN_NAMES = ( + SELF_NAMESPACE_MEMBERS | PYTHON_KEYWORDS | RESERVED_KEYWORDS | BUILTIN_CONSTANTS +) +ALLOWED_FN_NAMES = ALL_RESERVED_KEYWORDS - DISALLOWED_FN_NAMES + + +@pytest.mark.parametrize("constant", sorted(ALLOWED_FN_NAMES)) +def test_reserved_keywords_fns_pass(constant, get_contract, assert_compile_failed): + code = f""" +@external +def {constant}(var: int128): + pass + """ + assert get_contract(code) is not None -@pytest.mark.parametrize("constant", sorted(RESERVED_KEYWORDS_NOT_WHITELISTED)) -def test_reserved_keywords_fns(constant, get_contract, assert_compile_failed): +@pytest.mark.parametrize("constant", sorted(DISALLOWED_FN_NAMES)) +def test_reserved_keywords_fns_fail(constant, get_contract, assert_compile_failed): code = f""" @external def {constant}(var: int128): diff --git a/vyper/codegen/stmt.py b/vyper/codegen/stmt.py index 6bd63eddec..de9801a740 100644 --- a/vyper/codegen/stmt.py +++ b/vyper/codegen/stmt.py @@ -24,7 +24,7 @@ from vyper.codegen.expr import Expr from vyper.codegen.return_ import make_return_stmt from vyper.exceptions import CompilerPanic, StructureException, TypeCheckFailure -from vyper.semantics.types import DArrayT +from vyper.semantics.types import DArrayT, MemberFunctionT from vyper.semantics.types.shortcuts import INT256_T, UINT256_T @@ -123,24 +123,25 @@ def parse_Call(self): "append", "pop", ): - # TODO: consider moving this to builtins - darray = Expr(self.stmt.func.value, self.context).ir_node - args = [Expr(x, self.context).ir_node for x in self.stmt.args] - if self.stmt.func.attr == "append": - # sanity checks - assert len(args) == 1 - arg = args[0] - assert isinstance(darray.typ, DArrayT) - check_assign( - dummy_node_for_type(darray.typ.value_type), dummy_node_for_type(arg.typ) - ) - - return append_dyn_array(darray, arg) - else: - assert len(args) == 0 - return pop_dyn_array(darray, return_popped_item=False) - - elif is_self_function: + func_type = self.stmt.func._metadata["type"] + if isinstance(func_type, MemberFunctionT): + darray = Expr(self.stmt.func.value, self.context).ir_node + args = [Expr(x, self.context).ir_node for x in self.stmt.args] + if self.stmt.func.attr == "append": + # sanity checks + assert len(args) == 1 + arg = args[0] + assert isinstance(darray.typ, DArrayT) + check_assign( + dummy_node_for_type(darray.typ.value_type), dummy_node_for_type(arg.typ) + ) + + return append_dyn_array(darray, arg) + else: + assert len(args) == 0 + return pop_dyn_array(darray, return_popped_item=False) + + if is_self_function: return self_call.ir_for_self_call(self.stmt, self.context) else: return external_call.ir_for_external_call(self.stmt, self.context) diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index 596d07c892..db9b9d7c91 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -283,8 +283,7 @@ def visit_FunctionDef(self, node): func = ContractFunctionT.from_FunctionDef(node) try: - # TODO sketchy elision of namespace validation - self.namespace["self"].typ.add_member(func.name, func, skip_namespace_validation=True) + self.namespace["self"].typ.add_member(func.name, func) node._metadata["type"] = func except VyperException as exc: raise exc.with_annotation(node) from None diff --git a/vyper/semantics/namespace.py b/vyper/semantics/namespace.py index 752ef7ad96..9fd4e1f307 100644 --- a/vyper/semantics/namespace.py +++ b/vyper/semantics/namespace.py @@ -1,7 +1,6 @@ import contextlib import re -from vyper.evm.opcodes import OPCODES from vyper.exceptions import ( CompilerPanic, NamespaceCollision, @@ -89,6 +88,7 @@ def clear(self): def validate_assignment(self, attr): validate_identifier(attr) + if attr in self: obj = super().__getitem__(attr) raise NamespaceCollision(f"'{attr}' has already been declared as a {obj}") @@ -120,13 +120,10 @@ def override_global_namespace(ns): def validate_identifier(attr): - namespace = get_namespace() - if attr in namespace and attr not in [x for i in namespace._scopes for x in i]: - raise NamespaceCollision(f"Cannot assign to '{attr}', it is a builtin") - if attr.lower() in RESERVED_KEYWORDS or attr.upper() in OPCODES: - raise StructureException(f"'{attr}' is a reserved keyword") if not re.match("^[_a-zA-Z][a-zA-Z0-9_]*$", attr): raise StructureException(f"'{attr}' contains invalid character(s)") + if attr.lower() in RESERVED_KEYWORDS: + raise StructureException(f"'{attr}' is a reserved keyword") # Cannot be used for variable or member naming @@ -145,19 +142,7 @@ def validate_identifier(attr): "struct", "event", "enum", - # control flow - "if", - "for", - "while", - "until", - "pass", - "def", # EVM operations - "send", - "selfdestruct", - "assert", - "raise", - "throw", "unreachable", # special functions (no name mangling) "init", @@ -168,17 +153,11 @@ def validate_identifier(attr): "_default_", "___default___", "____default____", - # environment variables - "chainid", - "blockhash", - "timestamp", - "timedelta", # boolean literals "true", "false", # more control flow and special operations "this", - "continue", "range", # None sentinal value "none", @@ -198,15 +177,8 @@ def validate_identifier(attr): "mwei", "twei", "pwei", - # `address` members - "balance", - "codesize", - "codehash", - "code", - "is_contract", - # units - "units", # sentinal constant values + # TODO remove when these are removed from the language "zero_address", "empty_bytes32", "max_int128", diff --git a/vyper/semantics/types/__init__.py b/vyper/semantics/types/__init__.py index 222c34ee99..246dcfdf34 100644 --- a/vyper/semantics/types/__init__.py +++ b/vyper/semantics/types/__init__.py @@ -1,6 +1,7 @@ from . import primitives, subscriptable, user from .base import TYPE_T, KwargSettings, VyperType, is_type_t from .bytestrings import BytesT, StringT, _BytestringT +from .function import MemberFunctionT from .primitives import AddressT, BoolT, BytesM_T, DecimalT, IntegerT from .subscriptable import DArrayT, HashMapT, SArrayT, TupleT from .user import EnumT, EventT, InterfaceT, StructT diff --git a/vyper/semantics/types/base.py b/vyper/semantics/types/base.py index 4216b5e23e..0ac4f7b06d 100644 --- a/vyper/semantics/types/base.py +++ b/vyper/semantics/types/base.py @@ -65,7 +65,7 @@ def __init__(self, members: Optional[Dict] = None) -> None: for k, v in self._type_members.items(): # for builtin members like `contract.address` -- skip namespace # validation, as it introduces a dependency cycle - self.add_member(k, v, skip_namespace_validation=True) + self.add_member(k, v) members = members or {} for k, v in members.items(): @@ -277,13 +277,8 @@ def get_subscripted_type(self, node: vy_ast.Index) -> None: """ raise StructureException(f"'{self}' cannot be indexed into", node) - def add_member( - self, name: str, type_: "VyperType", skip_namespace_validation: bool = False - ) -> None: - # skip_namespace_validation provides a way of bypassing validate_identifier, which - # introduces a dependency cycle with the builtin_functions module - if not skip_namespace_validation: - validate_identifier(name) + def add_member(self, name: str, type_: "VyperType") -> None: + validate_identifier(name) if name in self.members: raise NamespaceCollision(f"Member '{name}' already exists in {self}") self.members[name] = type_ diff --git a/vyper/semantics/types/subscriptable.py b/vyper/semantics/types/subscriptable.py index fb4505fb20..a5ae075b73 100644 --- a/vyper/semantics/types/subscriptable.py +++ b/vyper/semantics/types/subscriptable.py @@ -216,16 +216,8 @@ def __init__(self, value_type: VyperType, length: int) -> None: from vyper.semantics.types.function import MemberFunctionT - self.add_member( - "append", - MemberFunctionT(self, "append", [self.value_type], None, True), - skip_namespace_validation=True, - ) - self.add_member( - "pop", - MemberFunctionT(self, "pop", [], self.value_type, True), - skip_namespace_validation=True, - ) + self.add_member("append", MemberFunctionT(self, "append", [self.value_type], None, True)) + self.add_member("pop", MemberFunctionT(self, "pop", [], self.value_type, True)) def __repr__(self): return f"DynArray[{self.value_type}, {self.length}]" diff --git a/vyper/utils.py b/vyper/utils.py index 2df78f1c2d..37a3f13b3d 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -257,9 +257,6 @@ class SizeLimits: MAX_UINT256 = 2**256 - 1 -# Otherwise reserved words that are whitelisted for function declarations -FUNCTION_WHITELIST = {"send"} - # List of valid IR macros. # TODO move this somewhere else, like ir_node.py VALID_IR_MACROS = {