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

WIP feat[lang]: @delegate decorator #4208

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
51 changes: 40 additions & 11 deletions vyper/semantics/types/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def __init__(
from_interface: bool = False,
nonreentrant: bool = False,
ast_def: Optional[vy_ast.VyperNode] = None,
delegate: bool = False,
) -> None:
super().__init__()

Expand All @@ -109,6 +110,7 @@ def __init__(
self.mutability = state_mutability
self.nonreentrant = nonreentrant
self.from_interface = from_interface
self.delegate = delegate

# sanity check, nonreentrant used to be Optional[str]
assert isinstance(self.nonreentrant, bool)
Expand Down Expand Up @@ -327,15 +329,20 @@ def from_vyi(cls, funcdef: vy_ast.FunctionDef) -> "ContractFunctionT":
-------
ContractFunctionT
"""
function_visibility, state_mutability, nonreentrant = _parse_decorators(funcdef)
function_visibility, state_mutability, nonreentrant_node, delegate_node = _parse_decorators(
funcdef
)

nonreentrant = nonreentrant_node is not None
if nonreentrant:
# TODO: refactor so parse_decorators returns the AST location
decorator = next(d for d in funcdef.decorator_list if d.id == "nonreentrant")
raise FunctionDeclarationException(
"`@nonreentrant` not allowed in interfaces", decorator
"`@nonreentrant` not allowed in interfaces", nonreentrant_node
)

delegate = delegate_node is not None
if delegate:
raise FunctionDeclarationException("`@delegate` not allowed in interfaces", delegate)

# it's redundant to specify visibility in vyi - always should be external
if function_visibility is None:
function_visibility = FunctionVisibility.EXTERNAL
Expand Down Expand Up @@ -395,12 +402,22 @@ def from_FunctionDef(cls, funcdef: vy_ast.FunctionDef) -> "ContractFunctionT":
-------
ContractFunctionT
"""
function_visibility, state_mutability, nonreentrant = _parse_decorators(funcdef)
function_visibility, state_mutability, nonreentrant_node, delegate_node = _parse_decorators(
funcdef
)

# it's redundant to specify internal visibility - it's implied by not being external
if function_visibility is None:
function_visibility = FunctionVisibility.INTERNAL

delegate = delegate_node is not None
if delegate and function_visibility != FunctionVisibility.EXTERNAL:
raise StructureException(
f"Cannot declare `@{function_visibility}` function as `@delegate`!", delegate
)

nonreentrant = nonreentrant_node is not None

positional_args, keyword_args = _parse_args(funcdef)

return_type = _parse_return_type(funcdef)
Expand Down Expand Up @@ -440,9 +457,9 @@ def from_FunctionDef(cls, funcdef: vy_ast.FunctionDef) -> "ContractFunctionT":
"Constructor may not use default arguments", funcdef.args.defaults[0]
)
if nonreentrant:
decorator = next(d for d in funcdef.decorator_list if d.id == "nonreentrant")
msg = "`@nonreentrant` decorator disallowed on `__init__`"
raise FunctionDeclarationException(msg, decorator)
raise FunctionDeclarationException(
"`@nonreentrant` decorator disallowed on `__init__`", nonreentrant_node
)

return cls(
funcdef.name,
Expand All @@ -454,6 +471,7 @@ def from_FunctionDef(cls, funcdef: vy_ast.FunctionDef) -> "ContractFunctionT":
from_interface=False,
nonreentrant=nonreentrant,
ast_def=funcdef,
delegate=delegate,
)

def set_reentrancy_key_position(self, position: VarOffset) -> None:
Expand Down Expand Up @@ -724,12 +742,19 @@ def _parse_return_type(funcdef: vy_ast.FunctionDef) -> Optional[VyperType]:
return type_from_annotation(funcdef.returns, DataLocation.MEMORY)


# TODO: simplify this
def _parse_decorators(
funcdef: vy_ast.FunctionDef,
) -> tuple[Optional[FunctionVisibility], StateMutability, bool]:
) -> tuple[
Optional[FunctionVisibility],
StateMutability,
Optional[vy_ast.VyperNode],
Optional[vy_ast.VyperNode],
]:
function_visibility = None
state_mutability = None
nonreentrant_node = None
delegate_node = None

for decorator in funcdef.decorator_list:
if isinstance(decorator, vy_ast.Call):
Expand All @@ -747,6 +772,11 @@ def _parse_decorators(

nonreentrant_node = decorator

elif decorator.get("id") == "delegate":
if delegate_node is not None:
raise StructureException("delegate decorator is already set", delegate_node)
delegate_node = decorator

elif isinstance(decorator, vy_ast.Name):
if FunctionVisibility.is_valid_value(decorator.id):
if function_visibility is not None:
Expand Down Expand Up @@ -785,8 +815,7 @@ def _parse_decorators(
if state_mutability == StateMutability.PURE and nonreentrant_node is not None:
raise StructureException("Cannot use reentrancy guard on pure functions", nonreentrant_node)

nonreentrant = nonreentrant_node is not None
return function_visibility, state_mutability, nonreentrant
return function_visibility, state_mutability, nonreentrant_node, delegate_node


def _parse_args(
Expand Down
Loading