-
-
Notifications
You must be signed in to change notification settings - Fork 905
refactor[codegen]: refactor venom builtins for uniformity and cleanliness #5001
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
base: master
Are you sure you want to change the base?
Changes from 11 commits
8832e9d
5229c75
9e9070f
29d696e
e2c3e45
85cf224
51da445
2a92b0d
f61bc2e
2f6f1e4
5075470
053367b
4409616
3ea8f27
3e8fa3a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,101 @@ | ||
| import pytest | ||
|
|
||
| from vyper import ast as vy_ast | ||
| from vyper.codegen_venom.builtins.abi import _get_bool_kwarg as get_abi_bool_kwarg | ||
| from vyper.codegen_venom.builtins.misc import _get_bool_kwarg as get_misc_bool_kwarg | ||
| from vyper.codegen_venom.builtins._kwargs import ( | ||
| get_bool_kwarg, | ||
| get_kwarg_ast_constants, | ||
| get_kwarg_values, | ||
| get_literal_kwarg, | ||
| kwarg_is_provided, | ||
| validate_kwargs, | ||
| ) | ||
| from vyper.exceptions import CompilerPanic | ||
|
|
||
|
|
||
| def _call_node(source): | ||
| return vy_ast.parse_to_ast(source).body[0].value | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("get_bool_kwarg", [get_abi_bool_kwarg, get_misc_bool_kwarg]) | ||
| def test_bool_kwarg_uses_reduced_value(get_bool_kwarg): | ||
| def test_kwarg_ast_constants_return_folded_nodes(): | ||
| call_node = _call_node("foo(flag=FLAG)") | ||
| call_node.keywords[0].value._set_folded_value(vy_ast.NameConstant(value=False)) | ||
|
|
||
| assert get_bool_kwarg(call_node, "flag", True) is False | ||
| constants = get_kwarg_ast_constants(call_node, ("flag",)) | ||
|
|
||
| assert constants["flag"].value is False | ||
|
|
||
| @pytest.mark.parametrize("get_bool_kwarg", [get_abi_bool_kwarg, get_misc_bool_kwarg]) | ||
| def test_bool_kwarg_rejects_unreduced_value(get_bool_kwarg): | ||
|
|
||
| def test_kwarg_ast_constants_reject_unfolded_values(): | ||
| call_node = _call_node("foo(flag=FLAG)") | ||
|
|
||
| with pytest.raises(CompilerPanic, match="unfoldable constant kwarg: flag"): | ||
| get_kwarg_ast_constants(call_node, ("flag",)) | ||
|
|
||
|
|
||
| def test_kwarg_helpers_reject_unexpected_kwargs(): | ||
| call_node = _call_node("foo(flag=False)") | ||
|
|
||
| with pytest.raises(CompilerPanic, match="unexpected kwarg: flag"): | ||
| validate_kwargs(call_node, ("other",)) | ||
|
|
||
|
|
||
| def test_kwarg_helpers_reject_duplicate_kwargs(): | ||
| call_node = _call_node("foo(flag=False, flag=True)") | ||
|
|
||
| with pytest.raises(CompilerPanic, match="duplicate kwarg: flag"): | ||
| validate_kwargs(call_node, ("flag",)) | ||
|
|
||
|
|
||
| def test_kwarg_is_provided(): | ||
| call_node = _call_node("foo(flag=False)") | ||
|
|
||
| assert kwarg_is_provided(call_node, "flag") is True | ||
| assert kwarg_is_provided(call_node, "other") is False | ||
|
|
||
|
|
||
| def test_bool_kwarg_uses_reduced_value(): | ||
| call_node = _call_node("foo(flag=FLAG)") | ||
| call_node.keywords[0].value._set_folded_value(vy_ast.NameConstant(value=False)) | ||
| constants = get_kwarg_ast_constants(call_node, ("flag",)) | ||
|
|
||
| assert get_bool_kwarg(constants, "flag", True) is False | ||
|
|
||
|
|
||
| def test_kwarg_constants_fill_defaults(): | ||
| call_node = _call_node("foo(flag=False)") | ||
|
|
||
| constants = get_kwarg_ast_constants(call_node, {"flag": True, "limit": 3, "salt": None}) | ||
|
|
||
| assert get_bool_kwarg(constants, "flag") is False | ||
| assert get_literal_kwarg(constants, "limit") == 3 | ||
| assert get_literal_kwarg(constants, "salt") is None | ||
|
|
||
|
|
||
| def test_kwarg_values_fill_late_defaults(): | ||
| call_node = _call_node("foo()") | ||
|
|
||
| values = get_kwarg_values(call_node, object(), {"gas": lambda: "gas-left", "value": 0}) | ||
|
|
||
| assert values == {"gas": "gas-left", "value": 0} | ||
|
|
||
|
|
||
| def test_bool_kwarg_rejects_unreduced_value(): | ||
| call_node = _call_node("foo(flag=FLAG)") | ||
|
|
||
| with pytest.raises(CompilerPanic, match="unfoldable boolean kwarg: flag"): | ||
| get_bool_kwarg(call_node, "flag", True) | ||
| with pytest.raises(CompilerPanic, match="unfoldable constant kwarg: flag"): | ||
| get_kwarg_ast_constants(call_node, ("flag",)) | ||
|
|
||
|
|
||
| def test_literal_kwarg_uses_reduced_value(): | ||
| call_node = _call_node("foo(revert_on_failure=REVERT)") | ||
| call_node.keywords[0].value._set_folded_value(vy_ast.NameConstant(value=False)) | ||
| constants = get_kwarg_ast_constants(call_node, ("revert_on_failure",)) | ||
|
|
||
| assert get_literal_kwarg(constants, "revert_on_failure", True) is False | ||
|
|
||
|
|
||
| def test_literal_kwarg_rejects_unreduced_value(): | ||
| call_node = _call_node("foo(revert_on_failure=REVERT)") | ||
|
|
||
| with pytest.raises(CompilerPanic, match="unfoldable constant kwarg: revert_on_failure"): | ||
| get_kwarg_ast_constants(call_node, ("revert_on_failure",)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from collections.abc import Iterable, Mapping | ||
| from dataclasses import dataclass | ||
| from typing import Any | ||
|
|
||
| from vyper import ast as vy_ast | ||
| from vyper.exceptions import CompilerPanic | ||
|
|
||
| _UNSET = object() | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class BuiltinCall: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. represents a builtin callsite with additional metadata
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a |
||
| node: vy_ast.Call | ||
| ctx: Any | ||
|
|
||
| @property | ||
| def args(self) -> list[vy_ast.VyperNode]: | ||
| return self.node.args | ||
|
|
||
| @property | ||
| def keywords(self) -> list[vy_ast.keyword]: | ||
| return self.node.keywords | ||
|
|
||
| def validate_kwargs(self, allowed_kwarg_names: Iterable[str]) -> None: | ||
| validate_kwargs(self.node, allowed_kwarg_names) | ||
|
|
||
| def get_kwarg_ast_constants( | ||
| self, | ||
| kwarg_defaults: Mapping[str, Any] | Iterable[str], | ||
| error_prefix: str = "unfoldable constant kwarg", | ||
| ) -> dict[str, Any]: | ||
| return get_kwarg_ast_constants(self.node, kwarg_defaults, error_prefix) | ||
|
|
||
| def get_kwarg_values(self, kwarg_defaults: Mapping[str, Any] | Iterable[str]): | ||
| return get_kwarg_values(self.node, self.ctx, kwarg_defaults) | ||
|
|
||
| def lower_pos_args(self, arg_nodes: Iterable[vy_ast.VyperNode] | None = None) -> list[Any]: | ||
| from vyper.codegen_venom.expr import Expr | ||
|
|
||
| arg_nodes = self.node.args if arg_nodes is None else arg_nodes | ||
| return [Expr(arg, self.ctx).lower() for arg in arg_nodes] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lower() or lower_value() should be called exactly once for every single arg/kwarg, in order, and it's not clear by inspection that (or why) this invariant holds
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clarified the invariant at the helper boundary: positional args are lowered in AST/source order, and explicit runtime kwargs are lowered once by iterating the collected keyword nodes in the user's keyword order rather than by allowed-name order. |
||
|
|
||
| def lower_pos_arg_values( | ||
| self, arg_nodes: Iterable[vy_ast.VyperNode] | None = None | ||
| ) -> list[Any]: | ||
| from vyper.codegen_venom.expr import Expr | ||
|
|
||
| arg_nodes = self.node.args if arg_nodes is None else arg_nodes | ||
| return [Expr(arg, self.ctx).lower_value() for arg in arg_nodes] | ||
|
|
||
|
|
||
| def _kwarg_names_and_defaults( | ||
| kwarg_defaults: Mapping[str, Any] | Iterable[str], | ||
| ) -> tuple[set[str], Mapping[str, Any]]: | ||
| if isinstance(kwarg_defaults, Mapping): | ||
| return set(kwarg_defaults), kwarg_defaults | ||
| return set(kwarg_defaults), {} | ||
|
|
||
|
|
||
| def _default_value(default): | ||
| if callable(default): | ||
| return default() | ||
| return default | ||
|
|
||
|
|
||
| def validate_kwargs(node: vy_ast.Call, allowed_kwarg_names: Iterable[str]) -> None: | ||
| seen = set() | ||
| for kw in node.keywords: | ||
| if kw.arg in seen: # pragma: nocover | ||
| raise CompilerPanic(f"duplicate kwarg: {kw.arg}", kw) | ||
| seen.add(kw.arg) | ||
|
|
||
| allowed_kwarg_names = set(allowed_kwarg_names) | ||
|
banteg marked this conversation as resolved.
Outdated
|
||
| for kw in node.keywords: | ||
|
banteg marked this conversation as resolved.
|
||
| if kw.arg not in allowed_kwarg_names: # pragma: nocover | ||
| raise CompilerPanic(f"unexpected kwarg: {kw.arg}", kw) | ||
|
|
||
|
|
||
| def kwarg_is_provided(node: vy_ast.Call, kwarg_name: str) -> bool: | ||
|
banteg marked this conversation as resolved.
Outdated
|
||
| return any(kw.arg == kwarg_name for kw in node.keywords) | ||
|
|
||
|
|
||
| def get_kwarg_ast_constants( | ||
| node: vy_ast.Call, | ||
| kwarg_defaults: Mapping[str, Any] | Iterable[str], | ||
|
banteg marked this conversation as resolved.
|
||
| error_prefix: str = "unfoldable constant kwarg", | ||
| ) -> dict[str, Any]: | ||
| kwarg_names, defaults = _kwarg_names_and_defaults(kwarg_defaults) | ||
| ret = {} | ||
| for kw in node.keywords: | ||
| if kw.arg not in kwarg_names: | ||
|
banteg marked this conversation as resolved.
Outdated
|
||
| continue | ||
|
|
||
| kw_node = kw.value.reduced() | ||
| if not isinstance(kw_node, vy_ast.Constant): # pragma: nocover | ||
| raise CompilerPanic(f"{error_prefix}: {kw.arg}", kw_node) | ||
| ret[kw.arg] = kw_node | ||
|
banteg marked this conversation as resolved.
Outdated
|
||
|
|
||
| for name, default in defaults.items(): | ||
| ret.setdefault(name, default) | ||
|
|
||
| return ret | ||
|
|
||
|
|
||
| def get_kwarg_values(node: vy_ast.Call, ctx, kwarg_defaults: Mapping[str, Any] | Iterable[str]): | ||
| from vyper.codegen_venom.expr import Expr | ||
|
|
||
| kwarg_names, defaults = _kwarg_names_and_defaults(kwarg_defaults) | ||
| ret = {} | ||
| for kw in node.keywords: | ||
| if kw.arg in kwarg_names: | ||
| ret[kw.arg] = Expr(kw.value.reduced(), ctx).lower_value() | ||
| for name, default in defaults.items(): | ||
| ret.setdefault(name, _default_value(default)) | ||
| return ret | ||
|
|
||
|
|
||
| def _literal_value(node: vy_ast.VyperNode) -> Any: | ||
| if isinstance(node, vy_ast.Int): | ||
| return node.value | ||
| if isinstance(node, vy_ast.NameConstant): | ||
| return node.value | ||
| return _UNSET | ||
|
|
||
|
|
||
| def get_bool_kwarg(kwarg_constants: dict[str, Any], kwarg_name: str, default: Any = _UNSET) -> bool: | ||
| kw_node = kwarg_constants.get(kwarg_name, _UNSET) | ||
| if kw_node is _UNSET: | ||
| if default is _UNSET: # pragma: nocover | ||
| raise CompilerPanic(f"missing boolean kwarg default: {kwarg_name}") | ||
| return default | ||
| if isinstance(kw_node, bool): | ||
| return kw_node | ||
| if isinstance(kw_node, vy_ast.NameConstant): | ||
| return kw_node.value | ||
| if isinstance(kw_node, vy_ast.Int): | ||
| return bool(kw_node.value) | ||
| raise CompilerPanic(f"unfoldable boolean kwarg: {kwarg_name}", kw_node) | ||
|
|
||
|
|
||
| def get_literal_kwarg(kwarg_constants: dict[str, Any], kwarg_name: str, default: Any = _UNSET): | ||
| kw_node = kwarg_constants.get(kwarg_name, _UNSET) | ||
| if kw_node is _UNSET: | ||
| if default is _UNSET: # pragma: nocover | ||
| raise CompilerPanic(f"missing literal kwarg default: {kwarg_name}") | ||
| return default | ||
| if kw_node is None or isinstance(kw_node, (bool, int)): | ||
| return kw_node | ||
|
|
||
| value = _literal_value(kw_node) | ||
| if value is not _UNSET: | ||
| return value | ||
|
|
||
| raise CompilerPanic(f"unfoldable literal kwarg: {kwarg_name}", kw_node) | ||
Uh oh!
There was an error while loading. Please reload this page.