Skip to content

Commit

Permalink
feat: relax restrictions on function names (#3307)
Browse files Browse the repository at this point in the history
see tests, but now functions are not checked against the 0th scope of
the namespace, including types and builtins. this should allow more
compatibility with external APIs that may require a function to be named
a certain way.
  • Loading branch information
tserg authored Apr 8, 2023
1 parent 11a78eb commit e8b5b3c
Show file tree
Hide file tree
Showing 13 changed files with 103 additions and 134 deletions.
8 changes: 0 additions & 8 deletions tests/parser/exceptions/test_structure_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,6 @@ def double_nonreentrant():
pass
""",
"""
CALLDATACOPY: int128
""",
"""
@external
def foo():
BALANCE: int128 = 45
""",
"""
@external
def foo():
true: int128 = 3
Expand Down
15 changes: 3 additions & 12 deletions tests/parser/syntax/test_address_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
),
],
)
Expand Down
18 changes: 18 additions & 0 deletions tests/parser/syntax/test_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 0 additions & 17 deletions tests/parser/syntax/test_structs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from vyper import compiler
from vyper.exceptions import (
InvalidType,
NamespaceCollision,
StructureException,
TypeMismatch,
UnknownAttribute,
Expand Down Expand Up @@ -421,22 +420,6 @@ def foo():
),
(
"""
struct X:
bar: int128
decimal: int128
""",
NamespaceCollision,
),
(
"""
struct B:
num: int128
address: address
""",
NamespaceCollision,
),
(
"""
struct Foo:
a: uint256
Expand Down
44 changes: 29 additions & 15 deletions tests/parser/syntax/utils/test_function_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -13,27 +13,15 @@ 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
""",
"""
@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
""",
]

Expand Down Expand Up @@ -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()
""",
]


Expand Down
30 changes: 22 additions & 8 deletions tests/parser/types/test_identifier_naming.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)


Expand Down Expand Up @@ -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):
Expand Down
39 changes: 20 additions & 19 deletions vyper/codegen/stmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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)
Expand Down
3 changes: 1 addition & 2 deletions vyper/semantics/analysis/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 4 additions & 32 deletions vyper/semantics/namespace.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import contextlib
import re

from vyper.evm.opcodes import OPCODES
from vyper.exceptions import (
CompilerPanic,
NamespaceCollision,
Expand Down Expand Up @@ -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}")
Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
1 change: 1 addition & 0 deletions vyper/semantics/types/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading

0 comments on commit e8b5b3c

Please sign in to comment.