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 15 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
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
64 changes: 39 additions & 25 deletions tests/parser/syntax/utils/test_function_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,13 @@
from vyper import compiler
from vyper.exceptions import NamespaceCollision, StructureException

fail_list = [ # noqa: E122
fail_list = [
"""
@external
def ő1qwerty(i: int128) -> int128:
temp_var : int128 = i
return temp_var
""",
"""
@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 wei(i: int128) -> int128:
temp_var : int128 = i
return temp_var
""",
"""
@external
def false(i: int128) -> int128:
temp_var : int128 = i
return temp_var
""",
]


Expand Down Expand Up @@ -63,6 +39,44 @@ 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 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 floor():
pass
""",
"""
@internal
def append():
pass

@external
def foo():
self.append()
""",
]


Expand Down
26 changes: 18 additions & 8 deletions tests/parser/types/test_identifier_naming.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from vyper.builtins.functions import BUILTIN_FUNCTIONS
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.namespace import PYTHON_KEYWORDS, RESERVED_KEYWORDS
from vyper.semantics.types.primitives import AddressT

ALL_RESERVED_KEYWORDS = (
set(BUILTIN_CONSTANTS.keys())
Expand Down Expand Up @@ -47,16 +47,26 @@ def test({constant}: int128):
)


RESERVED_KEYWORDS_NOT_WHITELISTED = sorted(ALL_RESERVED_KEYWORDS.difference(FUNCTION_WHITELIST))
SELF_NAMESPACE_MEMBERS = set(AddressT._type_members.keys())
DISALLOWED_FN_NAMES = SELF_NAMESPACE_MEMBERS.union(PYTHON_KEYWORDS)
ALLOWED_FN_NAMES = ALL_RESERVED_KEYWORDS - DISALLOWED_FN_NAMES


@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(ALLOWED_FN_NAMES))
def test_reserved_keywords_fns_pass(constant, get_contract, assert_compile_failed):
code = f"""
@external
def {constant}(var: int128):
pass
"""
assert_compile_failed(
lambda: get_contract(code), (SyntaxException, StructureException, NamespaceCollision)
)
assert get_contract(code) is not None


@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):
pass
"""
assert_compile_failed(lambda: get_contract(code), (SyntaxException, NamespaceCollision))
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
1 change: 0 additions & 1 deletion vyper/semantics/analysis/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,6 @@ 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)
node._metadata["type"] = func
except VyperException as exc:
Expand Down
Loading