Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow functions to be named after builtins and reserved keywords #3307

Merged
merged 23 commits into from
Apr 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i guess these are allowed now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes they are, similar to def decimal(): pass

""",
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):
charles-cooper marked this conversation as resolved.
Show resolved Hide resolved
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
charles-cooper marked this conversation as resolved.
Show resolved Hide resolved
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",
charles-cooper marked this conversation as resolved.
Show resolved Hide resolved
"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