From 50dc8fa5bb2f66c9185030513f03bda50f9769a9 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 8 Nov 2023 15:17:40 +0800 Subject: [PATCH 001/120] wip --- vyper/ast/expansion.py | 4 +- vyper/ast/folding.py | 10 +--- vyper/ast/nodes.py | 25 ++++++++++ vyper/compiler/phases.py | 10 ++-- vyper/semantics/analysis/__init__.py | 2 + vyper/semantics/analysis/local.py | 14 +++--- vyper/semantics/analysis/module.py | 2 + vyper/semantics/analysis/pre_typecheck.py | 58 +++++++++++++++++++++++ vyper/semantics/analysis/utils.py | 33 ++++++++++++- vyper/semantics/types/function.py | 4 +- 10 files changed, 137 insertions(+), 25 deletions(-) create mode 100644 vyper/semantics/analysis/pre_typecheck.py diff --git a/vyper/ast/expansion.py b/vyper/ast/expansion.py index 5471b971a4..c854ac13d2 100644 --- a/vyper/ast/expansion.py +++ b/vyper/ast/expansion.py @@ -108,8 +108,8 @@ def remove_unused_statements(vyper_module: vy_ast.Module) -> None: """ # constant declarations - values were substituted within the AST during folding - for node in vyper_module.get_children(vy_ast.VariableDecl, {"is_constant": True}): - vyper_module.remove_from_body(node) + #for node in vyper_module.get_children(vy_ast.VariableDecl, {"is_constant": True}): + # vyper_module.remove_from_body(node) # `implements: interface` statements - validated during type checking for node in vyper_module.get_children(vy_ast.ImplementsDecl): diff --git a/vyper/ast/folding.py b/vyper/ast/folding.py index 38d58f6fd0..ccefb8ae4e 100644 --- a/vyper/ast/folding.py +++ b/vyper/ast/folding.py @@ -146,14 +146,7 @@ def replace_user_defined_constants(vyper_module: vy_ast.Module) -> int: continue # Extract type definition from propagated annotation - type_ = None - try: - type_ = type_from_annotation(node.annotation) - except UnknownType: - # handle user-defined types e.g. structs - it's OK to not - # propagate the type annotation here because user-defined - # types can be unambiguously inferred at typechecking time - pass + type_ = node._metadata["type"] changed_nodes += replace_constant( vyper_module, node.target.id, node.value, False, type_=type_ @@ -188,6 +181,7 @@ def _replace(old_node, new_node, type_=None): new_node = new_node.from_node( old_node, func=new_node.func, args=new_node.args, keyword=keyword, keywords=keywords ) + new_node._metadata["type"] = type_ return new_node else: raise UnfoldableNode diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index 2497928035..5d931af9ea 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -912,6 +912,15 @@ class Name(ExprNode): class UnaryOp(ExprNode): __slots__ = ("op", "operand") + def prefold(self) -> ExprNode: + operand = self.operand._metadata.get("folded_value") + if operand is None: + return + + value = self.op._op(operand.value) + print("prefolded unary val: ", value) + return type(self.operand).from_node(self, value=value) + def evaluate(self) -> ExprNode: """ Attempt to evaluate the unary operation. @@ -960,6 +969,22 @@ def _op(self, value): class BinOp(ExprNode): __slots__ = ("left", "op", "right") + def prefold(self) -> ExprNode: + left = self.left._metadata.get("folded_value") + right = self.right._metadata.get("folded_value") + + if None in (left, right): + return + + # this validation is performed to prevent the compiler from hanging + # on very large shifts and improve the error message for negative + # values. + if isinstance(self.op, (LShift, RShift)) and not (0 <= right.value <= 256): + raise InvalidLiteral("Shift bits must be between 0 and 256", right) + + value = self.op._op(left.value, right.value) + return type(left).from_node(self, value=value) + def evaluate(self) -> ExprNode: """ Attempt to evaluate the arithmetic operation. diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index bfbb336d54..66c3ef08c8 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -271,13 +271,13 @@ def generate_folded_ast( vy_ast.validation.validate_literal_nodes(vyper_module) - vyper_module_folded = copy.deepcopy(vyper_module) - vy_ast.folding.fold(vyper_module_folded) - with input_bundle.search_path(contract_path.parent): - validate_semantics(vyper_module_folded, input_bundle) + validate_semantics(vyper_module, input_bundle) - symbol_tables = set_data_positions(vyper_module_folded, storage_layout_overrides) + symbol_tables = set_data_positions(vyper_module, storage_layout_overrides) + + vyper_module_folded = copy.deepcopy(vyper_module) + vy_ast.folding.fold(vyper_module_folded) return vyper_module_folded, symbol_tables diff --git a/vyper/semantics/analysis/__init__.py b/vyper/semantics/analysis/__init__.py index 7db230167e..69de8f9f04 100644 --- a/vyper/semantics/analysis/__init__.py +++ b/vyper/semantics/analysis/__init__.py @@ -4,6 +4,7 @@ from ..namespace import get_namespace from .local import validate_functions from .module import add_module_namespace +from .pre_typecheck import pre_typecheck from .utils import _ExprAnalyser @@ -12,6 +13,7 @@ def validate_semantics(vyper_ast, input_bundle): namespace = get_namespace() with namespace.enter_scope(): + pre_typecheck(vyper_ast) add_module_namespace(vyper_ast, input_bundle) vy_ast.expansion.expand_annotated_ast(vyper_ast) validate_functions(vyper_ast) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 647f01c299..88ac6d46bf 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -186,7 +186,7 @@ def __init__( self.fn_node = fn_node self.namespace = namespace self.func = fn_node._metadata["type"] - self.expr_visitor = _ExprVisitor(self.func) + self.expr_visitor = ExprVisitor(self.func) # allow internal function params to be mutable location, is_immutable = ( @@ -577,10 +577,10 @@ def visit_Return(self, node): self.expr_visitor.visit(node.value, self.func.return_type) -class _ExprVisitor(VyperNodeVisitorBase): +class ExprVisitor(VyperNodeVisitorBase): scope_name = "function" - def __init__(self, fn_node: ContractFunctionT): + def __init__(self, fn_node: Optional[ContractFunctionT] = None): self.func = fn_node def visit(self, node, typ): @@ -605,10 +605,10 @@ def visit_Attribute(self, node: vy_ast.Attribute, typ: VyperType) -> None: # if self.func.mutability < expr_info.mutability: # raise ... - if self.func.mutability != StateMutability.PAYABLE: + if self.func and self.func.mutability != StateMutability.PAYABLE: _validate_msg_value_access(node) - if self.func.mutability == StateMutability.PURE: + if self.func and self.func.mutability == StateMutability.PURE: _validate_pure_access(node, typ) value_type = get_exact_type_from_node(node.value) @@ -643,7 +643,7 @@ def visit_Call(self, node: vy_ast.Call, typ: VyperType) -> None: if isinstance(call_type, ContractFunctionT): # function calls - if call_type.is_internal: + if self.func and call_type.is_internal: self.func.called_functions.add(call_type) for arg, typ in zip(node.args, call_type.argument_types): self.visit(arg, typ) @@ -734,7 +734,7 @@ def visit_List(self, node: vy_ast.List, typ: VyperType) -> None: self.visit(element, typ.value_type) def visit_Name(self, node: vy_ast.Name, typ: VyperType) -> None: - if self.func.mutability == StateMutability.PURE: + if self.func and self.func.mutability == StateMutability.PURE: _validate_self_reference(node) if not isinstance(typ, TYPE_T): diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index 239438f35b..2cd9841e16 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -20,6 +20,7 @@ ) from vyper.semantics.analysis.base import VarInfo from vyper.semantics.analysis.common import VyperNodeVisitorBase +from vyper.semantics.analysis.local import ExprVisitor from vyper.semantics.analysis.utils import check_constant, validate_expected_type from vyper.semantics.data_locations import DataLocation from vyper.semantics.namespace import Namespace, get_namespace @@ -231,6 +232,7 @@ def _validate_self_namespace(): raise exc.with_annotation(node) from None if node.is_constant: + ExprVisitor().visit(node.value, type_) if not node.value: raise VariableDeclarationException("Constant must be declared with a value", node) if not check_constant(node.value): diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py new file mode 100644 index 0000000000..fde724e91d --- /dev/null +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -0,0 +1,58 @@ +from vyper import ast as vy_ast +from vyper.exceptions import UnfoldableNode +from vyper.semantics.analysis.common import VyperNodeVisitorBase + + +def get_constants(node: vy_ast.Module) -> dict: + constants = {} + module_nodes = node.body.copy() + const_var_decls = [ + n for n in module_nodes if isinstance(n, vy_ast.VariableDecl) and n.is_constant + ] + + while const_var_decls: + derived_nodes = 0 + + for c in const_var_decls: + name = c.get("target.id") + # Handle syntax errors downstream + if c.value is None: + continue + + prefold(c, constants) + + val = c._metadata.get("folded_value") + + # note that if a constant is redefined, its value will be overwritten, + # but it is okay because the syntax error is handled downstream + if val is not None: + self.constants[name] = val + derived_nodes += 1 + const_var_decls.remove(c) + + if not derived_nodes: + break + + return constants + + +def pre_typecheck(node: vy_ast.Module): + constants = get_constants(node) + + for n in node.get_descendants(reverse=True): + if isinstance(n, vy_ast.VariableDecl): + continue + + prefold(n, constants) + + +def prefold(node: vy_ast.VyperNode, constants: dict) -> None: + print("prefolding") + if isinstance(node, vy_ast.BinOp): + node._metadata["folded_value"] = node.prefold() + + if isinstance(node, vy_ast.UnaryOp): + node._metadata["folded_value"] = node.prefold() + + if isinstance(node, vy_ast.Constant): + node._metadata["folded_value"] = node \ No newline at end of file diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index afa6b56838..1a6f9ee606 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -620,8 +620,20 @@ def check_kwargable(node: vy_ast.VyperNode) -> bool: """ Check if the given node can be used as a default arg """ + print("check kwargable") if _check_literal(node): return True + if isinstance(node, vy_ast.Attribute): + res = check_kwargable(node.value) + print("check_constant - attribute: ", res) + return res + # if isinstance(node, vy_ast.Name): + # ns = get_namespace() + # varinfo = ns.get(node.id) + # if varinfo is None: + # return False + + # return varinfo.is_constant if isinstance(node, (vy_ast.Tuple, vy_ast.List)): return all(check_kwargable(item) for item in node.elements) if isinstance(node, vy_ast.Call): @@ -646,6 +658,9 @@ def _check_literal(node: vy_ast.VyperNode) -> bool: return True elif isinstance(node, (vy_ast.Tuple, vy_ast.List)): return all(_check_literal(item) for item in node.elements) + + if node._metadata.get("folded_value"): + return True return False @@ -655,6 +670,20 @@ def check_constant(node: vy_ast.VyperNode) -> bool: """ if _check_literal(node): return True + if isinstance(node, vy_ast.Attribute): + print("check_constant - attribute") + return check_constant(node.value) + if isinstance(node, vy_ast.Name): + ns = get_namespace() + varinfo = self.namespace.get(node.id) + if varinfo is None: + return False + + return varinfo.is_constant + + if isinstance(node, vy_ast.BinOp): + return all(check_kwargable(i) for i in (node.left, node.right)) + if isinstance(node, (vy_ast.Tuple, vy_ast.List)): return all(check_constant(item) for item in node.elements) if isinstance(node, vy_ast.Call): @@ -666,4 +695,6 @@ def check_constant(node: vy_ast.VyperNode) -> bool: if getattr(call_type, "_kwargable", False): return True - return False + value_type = get_expr_info(node) + # is_constant here actually means not_assignable, and is to be renamed + return value_type.is_constant diff --git a/vyper/semantics/types/function.py b/vyper/semantics/types/function.py index 77b9efb13d..b481ea07a1 100644 --- a/vyper/semantics/types/function.py +++ b/vyper/semantics/types/function.py @@ -17,7 +17,7 @@ StructureException, ) from vyper.semantics.analysis.base import FunctionVisibility, StateMutability, StorageSlot -from vyper.semantics.analysis.utils import check_kwargable, validate_expected_type +from vyper.semantics.analysis.utils import check_constant, validate_expected_type from vyper.semantics.data_locations import DataLocation from vyper.semantics.types.base import KwargSettings, VyperType from vyper.semantics.types.primitives import BoolT @@ -318,7 +318,7 @@ def from_FunctionDef( positional_args.append(PositionalArg(argname, type_, ast_source=arg)) else: value = node.args.defaults[i - n_positional_args] - if not check_kwargable(value): + if not check_constant(value): raise StateAccessViolation( "Value must be literal or environment variable", value ) From fafb447bd938319b36d55b21a79bf001482fa548 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 9 Nov 2023 23:27:16 +0800 Subject: [PATCH 002/120] use folded kwargs for fn defaults --- .../function_definitions/external_function.py | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/vyper/codegen/function_definitions/external_function.py b/vyper/codegen/function_definitions/external_function.py index 65276469e7..ee91ec754d 100644 --- a/vyper/codegen/function_definitions/external_function.py +++ b/vyper/codegen/function_definitions/external_function.py @@ -1,3 +1,4 @@ +from vyper import ast as vy_ast from vyper.codegen.abi_encoder import abi_encoding_matches_vyper from vyper.codegen.context import Context, VariableRecord from vyper.codegen.core import get_element_ptr, getpos, make_setter, needs_clamp @@ -50,7 +51,7 @@ def _register_function_args(func_t: ContractFunctionT, context: Context) -> list def _generate_kwarg_handlers( - func_t: ContractFunctionT, context: Context + func_t: ContractFunctionT, context: Context, code: vy_ast.FunctionDef ) -> dict[str, tuple[int, IRnode]]: # generate kwarg handlers. # since they might come in thru calldata or be default, @@ -62,7 +63,7 @@ def _generate_kwarg_handlers( # write default args to memory # goto external_function_common_ir - def handler_for(calldata_kwargs, default_kwargs): + def handler_for(calldata_kwargs, folded_default_kwargs, original_default_kwargs): calldata_args = func_t.positional_args + calldata_kwargs # create a fake type so that get_element_ptr works calldata_args_t = TupleT(list(arg.typ for arg in calldata_args)) @@ -81,7 +82,7 @@ def handler_for(calldata_kwargs, default_kwargs): calldata_min_size = args_abi_t.min_size() + 4 # TODO optimize make_setter by using - # TupleT(list(arg.typ for arg in calldata_kwargs + default_kwargs)) + # TupleT(list(arg.typ for arg in calldata_kwargs + folded_default_kwargs)) # (must ensure memory area is contiguous) for i, arg_meta in enumerate(calldata_kwargs): @@ -97,15 +98,15 @@ def handler_for(calldata_kwargs, default_kwargs): copy_arg.source_pos = getpos(arg_meta.ast_source) ret.append(copy_arg) - for x in default_kwargs: + for x, y in zip(original_default_kwargs, folded_default_kwargs): dst = context.lookup_var(x.name).pos lhs = IRnode(dst, location=MEMORY, typ=x.typ) - lhs.source_pos = getpos(x.ast_source) - kw_ast_val = func_t.default_values[x.name] # e.g. `3` in x: int = 3 + lhs.source_pos = getpos(y) + kw_ast_val = y rhs = Expr(kw_ast_val, context).ir_node copy_arg = make_setter(lhs, rhs) - copy_arg.source_pos = getpos(x.ast_source) + copy_arg.source_pos = getpos(y) ret.append(copy_arg) ret.append(["goto", func_t._ir_info.external_function_base_entry_label]) @@ -116,6 +117,7 @@ def handler_for(calldata_kwargs, default_kwargs): ret = {} keyword_args = func_t.keyword_args + folded_keyword_args = code.args.defaults # allocate variable slots in memory for arg in keyword_args: @@ -123,12 +125,15 @@ def handler_for(calldata_kwargs, default_kwargs): for i, _ in enumerate(keyword_args): calldata_kwargs = keyword_args[:i] - default_kwargs = keyword_args[i:] + # folded ast + original_default_kwargs = keyword_args[i:] + # unfolded ast + folded_default_kwargs = folded_keyword_args[1:] - sig, calldata_min_size, ir_node = handler_for(calldata_kwargs, default_kwargs) + sig, calldata_min_size, ir_node = handler_for(calldata_kwargs, folded_default_kwargs, original_default_kwargs) ret[sig] = calldata_min_size, ir_node - sig, calldata_min_size, ir_node = handler_for(keyword_args, []) + sig, calldata_min_size, ir_node = handler_for(keyword_args, [], []) ret[sig] = calldata_min_size, ir_node @@ -153,7 +158,7 @@ def generate_ir_for_external_function(code, func_t, context): handle_base_args = _register_function_args(func_t, context) # generate handlers for kwargs and register the variable records - kwarg_handlers = _generate_kwarg_handlers(func_t, context) + kwarg_handlers = _generate_kwarg_handlers(func_t, context, code) body = ["seq"] # once optional args have been handled, From 0f6b9295fa41f4147ae2858a261996584522fe2f Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 9 Nov 2023 23:28:21 +0800 Subject: [PATCH 003/120] separate expansion steps --- vyper/ast/expansion.py | 4 ++-- vyper/semantics/analysis/__init__.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/vyper/ast/expansion.py b/vyper/ast/expansion.py index c854ac13d2..fe8f374123 100644 --- a/vyper/ast/expansion.py +++ b/vyper/ast/expansion.py @@ -108,8 +108,8 @@ def remove_unused_statements(vyper_module: vy_ast.Module) -> None: """ # constant declarations - values were substituted within the AST during folding - #for node in vyper_module.get_children(vy_ast.VariableDecl, {"is_constant": True}): - # vyper_module.remove_from_body(node) + for node in vyper_module.get_children(vy_ast.VariableDecl, {"is_constant": True}): + vyper_module.remove_from_body(node) # `implements: interface` statements - validated during type checking for node in vyper_module.get_children(vy_ast.ImplementsDecl): diff --git a/vyper/semantics/analysis/__init__.py b/vyper/semantics/analysis/__init__.py index 69de8f9f04..52f212e8d6 100644 --- a/vyper/semantics/analysis/__init__.py +++ b/vyper/semantics/analysis/__init__.py @@ -15,5 +15,6 @@ def validate_semantics(vyper_ast, input_bundle): with namespace.enter_scope(): pre_typecheck(vyper_ast) add_module_namespace(vyper_ast, input_bundle) - vy_ast.expansion.expand_annotated_ast(vyper_ast) + vy_ast.expansion.generate_public_variable_getters(vyper_ast) validate_functions(vyper_ast) + vy_ast.expansion.remove_unused_statements(vyper_ast) From 6069f3bfd5459c740d08ff5d59c21535e1719b72 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 9 Nov 2023 23:30:43 +0800 Subject: [PATCH 004/120] add comment --- vyper/semantics/analysis/utils.py | 4 +++- vyper/semantics/types/function.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 1a6f9ee606..0ee845f84b 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -673,9 +673,11 @@ def check_constant(node: vy_ast.VyperNode) -> bool: if isinstance(node, vy_ast.Attribute): print("check_constant - attribute") return check_constant(node.value) + + # TODO: is this necessary? if isinstance(node, vy_ast.Name): ns = get_namespace() - varinfo = self.namespace.get(node.id) + varinfo = ns.get(node.id) if varinfo is None: return False diff --git a/vyper/semantics/types/function.py b/vyper/semantics/types/function.py index b481ea07a1..afbba95658 100644 --- a/vyper/semantics/types/function.py +++ b/vyper/semantics/types/function.py @@ -35,12 +35,14 @@ class _FunctionArg: @dataclass class PositionalArg(_FunctionArg): + # unfolded ast ast_source: Optional[vy_ast.VyperNode] = None @dataclass class KeywordArg(_FunctionArg): default_value: vy_ast.VyperNode + # unfolded ast ast_source: Optional[vy_ast.VyperNode] = None From 1c4bf3d0cff7190935160f89407f5e00fae137a1 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:02:01 +0800 Subject: [PATCH 005/120] move remove unused statements after folding --- vyper/compiler/phases.py | 2 ++ vyper/semantics/analysis/__init__.py | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 66c3ef08c8..ff586e300b 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -279,6 +279,8 @@ def generate_folded_ast( vyper_module_folded = copy.deepcopy(vyper_module) vy_ast.folding.fold(vyper_module_folded) + vy_ast.expansion.remove_unused_statements(vyper_module_folded) + return vyper_module_folded, symbol_tables diff --git a/vyper/semantics/analysis/__init__.py b/vyper/semantics/analysis/__init__.py index 52f212e8d6..5350874281 100644 --- a/vyper/semantics/analysis/__init__.py +++ b/vyper/semantics/analysis/__init__.py @@ -17,4 +17,3 @@ def validate_semantics(vyper_ast, input_bundle): add_module_namespace(vyper_ast, input_bundle) vy_ast.expansion.generate_public_variable_getters(vyper_ast) validate_functions(vyper_ast) - vy_ast.expansion.remove_unused_statements(vyper_ast) From 0aa3b69f1a45399a6196b6a15271a4c6502ba76c Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:24:51 +0800 Subject: [PATCH 006/120] fix get_index_value --- vyper/semantics/types/utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vyper/semantics/types/utils.py b/vyper/semantics/types/utils.py index 1187080ca9..9b66ef5c22 100644 --- a/vyper/semantics/types/utils.py +++ b/vyper/semantics/types/utils.py @@ -151,7 +151,8 @@ def get_index_value(node: vy_ast.Index) -> int: # TODO: revisit this! from vyper.semantics.analysis.utils import get_possible_types_from_node - if not isinstance(node.get("value"), vy_ast.Int): + value = node.value._metadata.get("folded_value") + if not isinstance(value, vy_ast.Int): if hasattr(node, "value"): # even though the subscript is an invalid type, first check if it's a valid _something_ # this gives a more accurate error in case of e.g. a typo in a constant variable name @@ -163,7 +164,7 @@ def get_index_value(node: vy_ast.Index) -> int: raise InvalidType("Subscript must be a literal integer", node) - if node.value.value <= 0: + if value.value <= 0: raise ArrayIndexException("Subscript must be greater than 0", node) - return node.value.value + return value.value From f2afc0dfda41d595b332031ab66833af0ce435a9 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 10 Nov 2023 23:13:51 +0800 Subject: [PATCH 007/120] wip --- vyper/ast/folding.py | 18 +++++++----- vyper/ast/nodes.py | 19 +++++++++++- vyper/builtins/_signatures.py | 6 ++-- vyper/builtins/functions.py | 5 ++++ vyper/semantics/analysis/local.py | 5 +++- vyper/semantics/analysis/pre_typecheck.py | 35 +++++++++++++++++++---- vyper/semantics/analysis/utils.py | 28 ++++-------------- vyper/semantics/types/subscriptable.py | 5 ++-- vyper/semantics/types/utils.py | 2 +- 9 files changed, 79 insertions(+), 44 deletions(-) diff --git a/vyper/ast/folding.py b/vyper/ast/folding.py index ccefb8ae4e..cc8e2dc4d1 100644 --- a/vyper/ast/folding.py +++ b/vyper/ast/folding.py @@ -49,6 +49,12 @@ def replace_literal_ops(vyper_module: vy_ast.Module) -> int: except UnfoldableNode: continue + # type may not be available if it is within a type's annotation + # e.g. DynArray[uint256, 2 ** 8] + typ = node._metadata.get("type") + if typ: + new_node._metadata["type"] = node._metadata["type"] + changed_nodes += 1 vyper_module.replace_in_tree(node, new_node) @@ -149,7 +155,7 @@ def replace_user_defined_constants(vyper_module: vy_ast.Module) -> int: type_ = node._metadata["type"] changed_nodes += replace_constant( - vyper_module, node.target.id, node.value, False, type_=type_ + vyper_module, node.target.id, node.value, type_, False ) return changed_nodes @@ -158,18 +164,16 @@ def replace_user_defined_constants(vyper_module: vy_ast.Module) -> int: # TODO constant folding on log events -def _replace(old_node, new_node, type_=None): +def _replace(old_node, new_node, type_): if isinstance(new_node, vy_ast.Constant): new_node = new_node.from_node(old_node, value=new_node.value) - if type_: - new_node._metadata["type"] = type_ + new_node._metadata["type"] = type_ return new_node elif isinstance(new_node, vy_ast.List): base_type = type_.value_type if type_ else None list_values = [_replace(old_node, i, type_=base_type) for i in new_node.elements] new_node = new_node.from_node(old_node, elements=list_values) - if type_: - new_node._metadata["type"] = type_ + new_node._metadata["type"] = type_ return new_node elif isinstance(new_node, vy_ast.Call): # Replace `Name` node with `Call` node @@ -191,8 +195,8 @@ def replace_constant( vyper_module: vy_ast.Module, id_: str, replacement_node: Union[vy_ast.Constant, vy_ast.List, vy_ast.Call], + type_: VyperType, raise_on_error: bool, - type_: Optional[VyperType] = None, ) -> int: """ Replace references to a variable name with a literal value. diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index 5d931af9ea..1c27103ca7 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -918,7 +918,6 @@ def prefold(self) -> ExprNode: return value = self.op._op(operand.value) - print("prefolded unary val: ", value) return type(self.operand).from_node(self, value=value) def evaluate(self) -> ExprNode: @@ -1135,6 +1134,14 @@ class RShift(Operator): class BoolOp(ExprNode): __slots__ = ("op", "values") + def prefold(self) -> ExprNode: + values = [i._metadata.get("folded_value") for i in self.values] + if None in values: + return + + value = self.op._op(values) + return NameConstant.from_node(self, value=value) + def evaluate(self) -> ExprNode: """ Attempt to evaluate the boolean operation. @@ -1191,6 +1198,16 @@ def __init__(self, *args, **kwargs): kwargs["right"] = kwargs.pop("comparators")[0] super().__init__(*args, **kwargs) + def prefold(self) -> ExprNode: + left = self.left._metadata.get("folded_value") + right = self.right._metadata.get("folded_value") + + if None in (left, right): + return + + value = self.op._op(left.value, right.value) + return NameConstant.from_node(self, value=value) + def evaluate(self) -> ExprNode: """ Attempt to evaluate the comparison. diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py index 2802421129..765789f0ec 100644 --- a/vyper/builtins/_signatures.py +++ b/vyper/builtins/_signatures.py @@ -6,7 +6,7 @@ from vyper.codegen.expr import Expr from vyper.codegen.ir_node import IRnode from vyper.exceptions import CompilerPanic, TypeMismatch -from vyper.semantics.analysis.utils import get_exact_type_from_node, validate_expected_type +from vyper.semantics.analysis.utils import check_constant, get_exact_type_from_node, validate_expected_type from vyper.semantics.types import TYPE_T, KwargSettings, VyperType from vyper.semantics.types.utils import type_from_annotation @@ -97,14 +97,14 @@ def _validate_arg_types(self, node): # note special meaning for -1 in validate_call_args API expect_num_args = (num_args, -1) - validate_call_args(node, expect_num_args, self._kwargs) + validate_call_args(node, expect_num_args, self._kwargs.keys()) for arg, (_, expected) in zip(node.args, self._inputs): self._validate_single(arg, expected) for kwarg in node.keywords: kwarg_settings = self._kwargs[kwarg.arg] - if kwarg_settings.require_literal and not isinstance(kwarg.value, vy_ast.Constant): + if kwarg_settings.require_literal and not check_constant(kwarg.value): raise TypeMismatch("Value for kwarg must be a literal", kwarg.value) self._validate_single(kwarg.value, kwarg_settings.typ) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 001939638b..04b87d97d9 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -111,6 +111,8 @@ class FoldedFunction(BuiltinFunction): # Since foldable builtin functions are not folded before semantics validation, # this flag is used for `check_kwargable` in semantics validation. _kwargable = True + # Skip annotation of builtins if it will be folded before codegen + _is_folded_before_codegen = True class TypenameFoldedFunction(FoldedFunction): @@ -2260,6 +2262,9 @@ def build_IR(self, expr, args, kwargs, context): class Empty(TypenameFoldedFunction): _id = "empty" + # Since `empty` is not folded in the AST, `is_folded` is set to False + # so that it will be properly annotated. + _is_folded_before_codegen = False def fetch_call_return(self, node): type_ = self.infer_arg_types(node)[0].typedef diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 88ac6d46bf..57de3cbe1a 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -352,7 +352,7 @@ def visit_For(self, node): kwargs = {s.arg: s.value for s in range_.keywords or []} if len(args) == 1: # range(CONSTANT) - n = args[0] + n = args[0]._metadata.get("folded_value") bound = kwargs.pop("bound", None) validate_expected_type(n, IntegerT.any()) @@ -668,6 +668,9 @@ def visit_Call(self, node: vy_ast.Call, typ: VyperType) -> None: for arg, arg_type in zip(node.args, call_type.arg_types): self.visit(arg, arg_type) else: + if getattr(call_type, "_is_folded_before_codegen", False): + return + # builtin functions arg_types = call_type.infer_arg_types(node) # `infer_arg_types` already calls `validate_expected_type` diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index fde724e91d..7d77fc096b 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -19,14 +19,15 @@ def get_constants(node: vy_ast.Module) -> dict: if c.value is None: continue - prefold(c, constants) + for n in c.value.get_descendants(include_self=True, reverse=True): + prefold(n, constants) - val = c._metadata.get("folded_value") + val = c.value._metadata.get("folded_value") # note that if a constant is redefined, its value will be overwritten, # but it is okay because the syntax error is handled downstream if val is not None: - self.constants[name] = val + constants[name] = val derived_nodes += 1 const_var_decls.remove(c) @@ -47,12 +48,34 @@ def pre_typecheck(node: vy_ast.Module): def prefold(node: vy_ast.VyperNode, constants: dict) -> None: - print("prefolding") if isinstance(node, vy_ast.BinOp): node._metadata["folded_value"] = node.prefold() if isinstance(node, vy_ast.UnaryOp): node._metadata["folded_value"] = node.prefold() - if isinstance(node, vy_ast.Constant): - node._metadata["folded_value"] = node \ No newline at end of file + if isinstance(node, (vy_ast.Constant, vy_ast.NameConstant)): + node._metadata["folded_value"] = node + + if isinstance(node, vy_ast.Compare): + node._metadata["folded_value"] = node.prefold() + + if isinstance(node, vy_ast.BoolOp): + node._metadata["folded_value"] = node.prefold() + + if isinstance(node, vy_ast.Name): + var_name = node.id + if var_name in constants: + node._metadata["folded_value"] = constants[var_name] + + if isinstance(node, vy_ast.Call): + if isinstance(node.func, vy_ast.Name): + from vyper.builtins.functions import DISPATCH_TABLE + func_name = node.func.id + + call_type = DISPATCH_TABLE.get(func_name) + if call_type and getattr(call_type, "_is_folded_before_codegen", False): + try: + node._metadata["folded_value"] = call_type.evaluate(node) # type: ignore + except UnfoldableNode: + pass diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 0ee845f84b..76ca64b6e5 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -620,20 +620,10 @@ def check_kwargable(node: vy_ast.VyperNode) -> bool: """ Check if the given node can be used as a default arg """ - print("check kwargable") if _check_literal(node): return True if isinstance(node, vy_ast.Attribute): - res = check_kwargable(node.value) - print("check_constant - attribute: ", res) - return res - # if isinstance(node, vy_ast.Name): - # ns = get_namespace() - # varinfo = ns.get(node.id) - # if varinfo is None: - # return False - - # return varinfo.is_constant + return check_kwargable(node.value) if isinstance(node, (vy_ast.Tuple, vy_ast.List)): return all(check_kwargable(item) for item in node.elements) if isinstance(node, vy_ast.Call): @@ -670,24 +660,16 @@ def check_constant(node: vy_ast.VyperNode) -> bool: """ if _check_literal(node): return True - if isinstance(node, vy_ast.Attribute): - print("check_constant - attribute") - return check_constant(node.value) - - # TODO: is this necessary? - if isinstance(node, vy_ast.Name): - ns = get_namespace() - varinfo = ns.get(node.id) - if varinfo is None: - return False - - return varinfo.is_constant if isinstance(node, vy_ast.BinOp): return all(check_kwargable(i) for i in (node.left, node.right)) + + if isinstance(node, vy_ast.BoolOp): + return all(check_kwargable(i) for i in node.values) if isinstance(node, (vy_ast.Tuple, vy_ast.List)): return all(check_constant(item) for item in node.elements) + if isinstance(node, vy_ast.Call): args = node.args if len(args) == 1 and isinstance(args[0], vy_ast.Dict): diff --git a/vyper/semantics/types/subscriptable.py b/vyper/semantics/types/subscriptable.py index 6a2d3aae73..db9c9b6f0f 100644 --- a/vyper/semantics/types/subscriptable.py +++ b/vyper/semantics/types/subscriptable.py @@ -274,11 +274,12 @@ def compare_type(self, other): @classmethod def from_annotation(cls, node: vy_ast.Subscript) -> "DArrayT": + length = node.slice.value.elements[1]._metadata.get("folded_value") if ( not isinstance(node, vy_ast.Subscript) or not isinstance(node.slice, vy_ast.Index) or not isinstance(node.slice.value, vy_ast.Tuple) - or not isinstance(node.slice.value.elements[1], vy_ast.Int) + or not isinstance(length, vy_ast.Int) or len(node.slice.value.elements) != 2 ): raise StructureException( @@ -290,7 +291,7 @@ def from_annotation(cls, node: vy_ast.Subscript) -> "DArrayT": if not value_type._as_darray: raise StructureException(f"Arrays of {value_type} are not allowed", node) - max_length = node.slice.value.elements[1].value + max_length = length.value return cls(value_type, max_length) diff --git a/vyper/semantics/types/utils.py b/vyper/semantics/types/utils.py index 9b66ef5c22..8a95a879d4 100644 --- a/vyper/semantics/types/utils.py +++ b/vyper/semantics/types/utils.py @@ -151,7 +151,7 @@ def get_index_value(node: vy_ast.Index) -> int: # TODO: revisit this! from vyper.semantics.analysis.utils import get_possible_types_from_node - value = node.value._metadata.get("folded_value") + value = node.value if isinstance(node.value, vy_ast.Int) else node.value._metadata.get("folded_value") if not isinstance(value, vy_ast.Int): if hasattr(node, "value"): # even though the subscript is an invalid type, first check if it's a valid _something_ From 7f75749aaa669c9e9f841d1a0dff2f017a9a35e9 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 10 Nov 2023 23:15:23 +0800 Subject: [PATCH 008/120] fix lint --- vyper/ast/expansion.py | 2 +- vyper/ast/folding.py | 9 +++------ vyper/ast/nodes.py | 2 +- vyper/builtins/_signatures.py | 7 +++++-- vyper/codegen/function_definitions/external_function.py | 4 +++- vyper/semantics/analysis/pre_typecheck.py | 8 ++++---- vyper/semantics/analysis/utils.py | 2 +- vyper/semantics/types/utils.py | 6 +++++- 8 files changed, 23 insertions(+), 17 deletions(-) diff --git a/vyper/ast/expansion.py b/vyper/ast/expansion.py index fe8f374123..5471b971a4 100644 --- a/vyper/ast/expansion.py +++ b/vyper/ast/expansion.py @@ -109,7 +109,7 @@ def remove_unused_statements(vyper_module: vy_ast.Module) -> None: # constant declarations - values were substituted within the AST during folding for node in vyper_module.get_children(vy_ast.VariableDecl, {"is_constant": True}): - vyper_module.remove_from_body(node) + vyper_module.remove_from_body(node) # `implements: interface` statements - validated during type checking for node in vyper_module.get_children(vy_ast.ImplementsDecl): diff --git a/vyper/ast/folding.py b/vyper/ast/folding.py index cc8e2dc4d1..1121903109 100644 --- a/vyper/ast/folding.py +++ b/vyper/ast/folding.py @@ -1,10 +1,9 @@ -from typing import Optional, Union +from typing import Union from vyper.ast import nodes as vy_ast from vyper.builtins.functions import DISPATCH_TABLE -from vyper.exceptions import UnfoldableNode, UnknownType +from vyper.exceptions import UnfoldableNode from vyper.semantics.types.base import VyperType -from vyper.semantics.types.utils import type_from_annotation def fold(vyper_module: vy_ast.Module) -> None: @@ -154,9 +153,7 @@ def replace_user_defined_constants(vyper_module: vy_ast.Module) -> int: # Extract type definition from propagated annotation type_ = node._metadata["type"] - changed_nodes += replace_constant( - vyper_module, node.target.id, node.value, type_, False - ) + changed_nodes += replace_constant(vyper_module, node.target.id, node.value, type_, False) return changed_nodes diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index 1c27103ca7..ca0c986fe0 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -916,7 +916,7 @@ def prefold(self) -> ExprNode: operand = self.operand._metadata.get("folded_value") if operand is None: return - + value = self.op._op(operand.value) return type(self.operand).from_node(self, value=value) diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py index 765789f0ec..332094cc0f 100644 --- a/vyper/builtins/_signatures.py +++ b/vyper/builtins/_signatures.py @@ -1,12 +1,15 @@ import functools from typing import Dict -from vyper.ast import nodes as vy_ast from vyper.ast.validation import validate_call_args from vyper.codegen.expr import Expr from vyper.codegen.ir_node import IRnode from vyper.exceptions import CompilerPanic, TypeMismatch -from vyper.semantics.analysis.utils import check_constant, get_exact_type_from_node, validate_expected_type +from vyper.semantics.analysis.utils import ( + check_constant, + get_exact_type_from_node, + validate_expected_type, +) from vyper.semantics.types import TYPE_T, KwargSettings, VyperType from vyper.semantics.types.utils import type_from_annotation diff --git a/vyper/codegen/function_definitions/external_function.py b/vyper/codegen/function_definitions/external_function.py index ee91ec754d..bd409012dc 100644 --- a/vyper/codegen/function_definitions/external_function.py +++ b/vyper/codegen/function_definitions/external_function.py @@ -130,7 +130,9 @@ def handler_for(calldata_kwargs, folded_default_kwargs, original_default_kwargs) # unfolded ast folded_default_kwargs = folded_keyword_args[1:] - sig, calldata_min_size, ir_node = handler_for(calldata_kwargs, folded_default_kwargs, original_default_kwargs) + sig, calldata_min_size, ir_node = handler_for( + calldata_kwargs, folded_default_kwargs, original_default_kwargs + ) ret[sig] = calldata_min_size, ir_node sig, calldata_min_size, ir_node = handler_for(keyword_args, [], []) diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index 7d77fc096b..d175604e3f 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -1,6 +1,5 @@ from vyper import ast as vy_ast from vyper.exceptions import UnfoldableNode -from vyper.semantics.analysis.common import VyperNodeVisitorBase def get_constants(node: vy_ast.Module) -> dict: @@ -39,11 +38,11 @@ def get_constants(node: vy_ast.Module) -> dict: def pre_typecheck(node: vy_ast.Module): constants = get_constants(node) - + for n in node.get_descendants(reverse=True): if isinstance(n, vy_ast.VariableDecl): continue - + prefold(n, constants) @@ -53,7 +52,7 @@ def prefold(node: vy_ast.VyperNode, constants: dict) -> None: if isinstance(node, vy_ast.UnaryOp): node._metadata["folded_value"] = node.prefold() - + if isinstance(node, (vy_ast.Constant, vy_ast.NameConstant)): node._metadata["folded_value"] = node @@ -71,6 +70,7 @@ def prefold(node: vy_ast.VyperNode, constants: dict) -> None: if isinstance(node, vy_ast.Call): if isinstance(node.func, vy_ast.Name): from vyper.builtins.functions import DISPATCH_TABLE + func_name = node.func.id call_type = DISPATCH_TABLE.get(func_name) diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 76ca64b6e5..7d71c6c948 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -663,7 +663,7 @@ def check_constant(node: vy_ast.VyperNode) -> bool: if isinstance(node, vy_ast.BinOp): return all(check_kwargable(i) for i in (node.left, node.right)) - + if isinstance(node, vy_ast.BoolOp): return all(check_kwargable(i) for i in node.values) diff --git a/vyper/semantics/types/utils.py b/vyper/semantics/types/utils.py index 8a95a879d4..4a1dd98024 100644 --- a/vyper/semantics/types/utils.py +++ b/vyper/semantics/types/utils.py @@ -151,7 +151,11 @@ def get_index_value(node: vy_ast.Index) -> int: # TODO: revisit this! from vyper.semantics.analysis.utils import get_possible_types_from_node - value = node.value if isinstance(node.value, vy_ast.Int) else node.value._metadata.get("folded_value") + value = ( + node.value + if isinstance(node.value, vy_ast.Int) + else node.value._metadata.get("folded_value") + ) if not isinstance(value, vy_ast.Int): if hasattr(node, "value"): # even though the subscript is an invalid type, first check if it's a valid _something_ From 415e123a7d28a0273cbd331858ec404e29ecba70 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 12 Nov 2023 17:46:57 +0800 Subject: [PATCH 009/120] add compile time and runtime constants attributes --- vyper/ast/folding.py | 1 + vyper/builtins/_signatures.py | 5 +++-- vyper/semantics/analysis/base.py | 17 ++++++++++------- vyper/semantics/analysis/local.py | 4 ++-- vyper/semantics/analysis/module.py | 3 ++- vyper/semantics/analysis/utils.py | 22 +++++++++++++--------- vyper/semantics/environment.py | 2 +- vyper/semantics/types/function.py | 4 ++-- 8 files changed, 34 insertions(+), 24 deletions(-) diff --git a/vyper/ast/folding.py b/vyper/ast/folding.py index 1121903109..17ae0a9da7 100644 --- a/vyper/ast/folding.py +++ b/vyper/ast/folding.py @@ -116,6 +116,7 @@ def replace_builtin_functions(vyper_module: vy_ast.Module) -> int: continue try: new_node = func.evaluate(node) # type: ignore + new_node._metadata["type"] = node._metadata["type"] except UnfoldableNode: continue diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py index 332094cc0f..82c43e705b 100644 --- a/vyper/builtins/_signatures.py +++ b/vyper/builtins/_signatures.py @@ -1,12 +1,13 @@ import functools from typing import Dict + from vyper.ast.validation import validate_call_args from vyper.codegen.expr import Expr from vyper.codegen.ir_node import IRnode from vyper.exceptions import CompilerPanic, TypeMismatch from vyper.semantics.analysis.utils import ( - check_constant, + check_kwargable, get_exact_type_from_node, validate_expected_type, ) @@ -107,7 +108,7 @@ def _validate_arg_types(self, node): for kwarg in node.keywords: kwarg_settings = self._kwargs[kwarg.arg] - if kwarg_settings.require_literal and not check_constant(kwarg.value): + if kwarg_settings.require_literal and not check_kwargable(kwarg.value): raise TypeMismatch("Value for kwarg must be a literal", kwarg.value) self._validate_single(kwarg.value, kwarg_settings.typ) diff --git a/vyper/semantics/analysis/base.py b/vyper/semantics/analysis/base.py index 449e6ca338..041575ee63 100644 --- a/vyper/semantics/analysis/base.py +++ b/vyper/semantics/analysis/base.py @@ -159,7 +159,8 @@ class VarInfo: typ: VyperType location: DataLocation = DataLocation.UNSET - is_constant: bool = False + is_compile_time_constant: bool = False + is_runtime_constant: bool = False is_public: bool = False is_immutable: bool = False is_transient: bool = False @@ -192,11 +193,12 @@ class ExprInfo: typ: VyperType var_info: Optional[VarInfo] = None location: DataLocation = DataLocation.UNSET - is_constant: bool = False + is_compile_time_constant: bool = False + is_runtime_constant: bool = False is_immutable: bool = False def __post_init__(self): - should_match = ("typ", "location", "is_constant", "is_immutable") + should_match = ("typ", "location", "is_compile_time_constant", "is_runtime_constant", "is_immutable") if self.var_info is not None: for attr in should_match: if getattr(self.var_info, attr) != getattr(self, attr): @@ -208,15 +210,16 @@ def from_varinfo(cls, var_info: VarInfo) -> "ExprInfo": var_info.typ, var_info=var_info, location=var_info.location, - is_constant=var_info.is_constant, - is_immutable=var_info.is_immutable, + is_compile_time_constant=var_info.is_compile_time_constant, + is_runtime_constant=var_info.is_runtime_constant, + is_immutable=var_info.is_immutable ) def copy_with_type(self, typ: VyperType) -> "ExprInfo": """ Return a copy of the ExprInfo but with the type set to something else """ - to_copy = ("location", "is_constant", "is_immutable") + to_copy = ("location", "is_compile_time_constant", "is_runtime_constant", "is_immutable") fields = {k: getattr(self, k) for k in to_copy} return self.__class__(typ=typ, **fields) @@ -240,7 +243,7 @@ def validate_modification(self, node: vy_ast.VyperNode, mutability: StateMutabil if self.location == DataLocation.CALLDATA: raise ImmutableViolation("Cannot write to calldata", node) - if self.is_constant: + if self.is_compile_time_constant: raise ImmutableViolation("Constant value cannot be written to", node) if self.is_immutable: if node.get_ancestor(vy_ast.FunctionDef).get("name") != "__init__": diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 57de3cbe1a..53dbb40178 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -193,7 +193,7 @@ def __init__( (DataLocation.MEMORY, False) if self.func.is_internal else (DataLocation.CALLDATA, True) ) for arg in self.func.arguments: - namespace[arg.name] = VarInfo(arg.typ, location=location, is_immutable=is_immutable) + namespace[arg.name] = VarInfo(arg.typ, location=location, is_immutable=is_immutable, is_runtime_constant=is_immutable) for node in fn_node.body: self.visit(node) @@ -473,7 +473,7 @@ def visit_For(self, node): with self.namespace.enter_scope(): try: - self.namespace[iter_name] = VarInfo(possible_target_type, is_constant=True) + self.namespace[iter_name] = VarInfo(possible_target_type, is_compile_time_constant=True) except VyperException as exc: raise exc.with_annotation(node) from None diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index 2cd9841e16..ecfff33a46 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -195,9 +195,10 @@ def visit_VariableDecl(self, node): type_, decl_node=node, location=data_loc, - is_constant=node.is_constant, + is_compile_time_constant=node.is_constant, is_public=node.is_public, is_immutable=node.is_immutable, + is_runtime_constant=node.is_immutable, is_transient=node.is_transient, ) node.target._metadata["varinfo"] = var_info # TODO maybe put this in the global namespace diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 7d71c6c948..237792ef03 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -195,7 +195,7 @@ def _raise_invalid_reference(name, node): if isinstance(s, VyperType): # ex. foo.bar(). bar() is a ContractFunctionT return [s] - if is_self_reference and (s.is_constant or s.is_immutable): + if is_self_reference and (s.is_compile_time_constant or s.is_immutable): _raise_invalid_reference(name, node) # general case. s is a VarInfo, e.g. self.foo return [s.typ] @@ -622,10 +622,16 @@ def check_kwargable(node: vy_ast.VyperNode) -> bool: """ if _check_literal(node): return True - if isinstance(node, vy_ast.Attribute): - return check_kwargable(node.value) + + if isinstance(node, vy_ast.BinOp): + return all(check_kwargable(i) for i in (node.left, node.right)) + + if isinstance(node, vy_ast.BoolOp): + return all(check_kwargable(i) for i in node.values) + if isinstance(node, (vy_ast.Tuple, vy_ast.List)): return all(check_kwargable(item) for item in node.elements) + if isinstance(node, vy_ast.Call): args = node.args if len(args) == 1 and isinstance(args[0], vy_ast.Dict): @@ -636,8 +642,7 @@ def check_kwargable(node: vy_ast.VyperNode) -> bool: return True value_type = get_expr_info(node) - # is_constant here actually means not_assignable, and is to be renamed - return value_type.is_constant + return value_type.is_runtime_constant def _check_literal(node: vy_ast.VyperNode) -> bool: @@ -662,10 +667,10 @@ def check_constant(node: vy_ast.VyperNode) -> bool: return True if isinstance(node, vy_ast.BinOp): - return all(check_kwargable(i) for i in (node.left, node.right)) + return all(check_constant(i) for i in (node.left, node.right)) if isinstance(node, vy_ast.BoolOp): - return all(check_kwargable(i) for i in node.values) + return all(check_constant(i) for i in node.values) if isinstance(node, (vy_ast.Tuple, vy_ast.List)): return all(check_constant(item) for item in node.elements) @@ -680,5 +685,4 @@ def check_constant(node: vy_ast.VyperNode) -> bool: return True value_type = get_expr_info(node) - # is_constant here actually means not_assignable, and is to be renamed - return value_type.is_constant + return value_type.is_compile_time_constant diff --git a/vyper/semantics/environment.py b/vyper/semantics/environment.py index ad68f1103e..0eeb803eb7 100644 --- a/vyper/semantics/environment.py +++ b/vyper/semantics/environment.py @@ -52,7 +52,7 @@ def get_constant_vars() -> Dict: """ result = {} for k, v in CONSTANT_ENVIRONMENT_VARS.items(): - result[k] = VarInfo(v, is_constant=True) + result[k] = VarInfo(v, is_runtime_constant=True) return result diff --git a/vyper/semantics/types/function.py b/vyper/semantics/types/function.py index afbba95658..ee43939320 100644 --- a/vyper/semantics/types/function.py +++ b/vyper/semantics/types/function.py @@ -17,7 +17,7 @@ StructureException, ) from vyper.semantics.analysis.base import FunctionVisibility, StateMutability, StorageSlot -from vyper.semantics.analysis.utils import check_constant, validate_expected_type +from vyper.semantics.analysis.utils import check_kwargable, validate_expected_type from vyper.semantics.data_locations import DataLocation from vyper.semantics.types.base import KwargSettings, VyperType from vyper.semantics.types.primitives import BoolT @@ -320,7 +320,7 @@ def from_FunctionDef( positional_args.append(PositionalArg(argname, type_, ast_source=arg)) else: value = node.args.defaults[i - n_positional_args] - if not check_constant(value): + if not check_kwargable(value): raise StateAccessViolation( "Value must be literal or environment variable", value ) From 1557927a4add8a5b59da4b9cd514c13612cdde47 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 10:06:56 +0800 Subject: [PATCH 010/120] fix list tests --- tests/functional/codegen/types/test_dynamic_array.py | 4 ++-- tests/functional/codegen/types/test_lists.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/functional/codegen/types/test_dynamic_array.py b/tests/functional/codegen/types/test_dynamic_array.py index 9231d1979f..41a8984555 100644 --- a/tests/functional/codegen/types/test_dynamic_array.py +++ b/tests/functional/codegen/types/test_dynamic_array.py @@ -1718,7 +1718,7 @@ def test_constant_list_fail(get_contract, assert_compile_failed, storage_type, r def foo() -> DynArray[{return_type}, 3]: return MY_CONSTANT """ - assert_compile_failed(lambda: get_contract(code), InvalidType) + assert_compile_failed(lambda: get_contract(code), TypeMismatch) @pytest.mark.parametrize("storage_type,return_type", itertools.permutations(integer_types, 2)) @@ -1730,7 +1730,7 @@ def test_constant_list_fail_2(get_contract, assert_compile_failed, storage_type, def foo() -> {return_type}: return MY_CONSTANT[0] """ - assert_compile_failed(lambda: get_contract(code), InvalidType) + assert_compile_failed(lambda: get_contract(code), TypeMismatch) @pytest.mark.parametrize("storage_type,return_type", itertools.permutations(integer_types, 2)) diff --git a/tests/functional/codegen/types/test_lists.py b/tests/functional/codegen/types/test_lists.py index 832b679e5e..a9e09a12eb 100644 --- a/tests/functional/codegen/types/test_lists.py +++ b/tests/functional/codegen/types/test_lists.py @@ -2,7 +2,7 @@ import pytest -from vyper.exceptions import ArrayIndexException, InvalidType, OverflowException, TypeMismatch +from vyper.exceptions import ArrayIndexException, OverflowException, TypeMismatch def test_list_tester_code(get_contract_with_gas_estimation): @@ -701,7 +701,7 @@ def test_constant_list_fail(get_contract, assert_compile_failed, storage_type, r def foo() -> {return_type}[3]: return MY_CONSTANT """ - assert_compile_failed(lambda: get_contract(code), InvalidType) + assert_compile_failed(lambda: get_contract(code), TypeMismatch) @pytest.mark.parametrize("storage_type,return_type", itertools.permutations(integer_types, 2)) @@ -713,7 +713,7 @@ def test_constant_list_fail_2(get_contract, assert_compile_failed, storage_type, def foo() -> {return_type}: return MY_CONSTANT[0] """ - assert_compile_failed(lambda: get_contract(code), InvalidType) + assert_compile_failed(lambda: get_contract(code), TypeMismatch) @pytest.mark.parametrize("storage_type,return_type", itertools.permutations(integer_types, 2)) @@ -817,7 +817,7 @@ def test_constant_nested_list_fail(get_contract, assert_compile_failed, storage_ def foo() -> {return_type}[2][3]: return MY_CONSTANT """ - assert_compile_failed(lambda: get_contract(code), InvalidType) + assert_compile_failed(lambda: get_contract(code), TypeMismatch) @pytest.mark.parametrize("storage_type,return_type", itertools.permutations(integer_types, 2)) @@ -831,4 +831,4 @@ def test_constant_nested_list_fail_2( def foo() -> {return_type}: return MY_CONSTANT[0][0] """ - assert_compile_failed(lambda: get_contract(code), InvalidType) + assert_compile_failed(lambda: get_contract(code), TypeMismatch) From 0bebd49f1d51346bc04746aa8a043a3b627f3bb8 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 10:07:07 +0800 Subject: [PATCH 011/120] fix tuple exprinfo ctor; fix lint --- vyper/builtins/_signatures.py | 1 - vyper/semantics/analysis/base.py | 10 ++++++++-- vyper/semantics/analysis/local.py | 11 +++++++++-- vyper/semantics/analysis/utils.py | 11 +++++++++-- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py index 82c43e705b..9fd59db060 100644 --- a/vyper/builtins/_signatures.py +++ b/vyper/builtins/_signatures.py @@ -1,7 +1,6 @@ import functools from typing import Dict - from vyper.ast.validation import validate_call_args from vyper.codegen.expr import Expr from vyper.codegen.ir_node import IRnode diff --git a/vyper/semantics/analysis/base.py b/vyper/semantics/analysis/base.py index 041575ee63..f38c39913f 100644 --- a/vyper/semantics/analysis/base.py +++ b/vyper/semantics/analysis/base.py @@ -198,7 +198,13 @@ class ExprInfo: is_immutable: bool = False def __post_init__(self): - should_match = ("typ", "location", "is_compile_time_constant", "is_runtime_constant", "is_immutable") + should_match = ( + "typ", + "location", + "is_compile_time_constant", + "is_runtime_constant", + "is_immutable", + ) if self.var_info is not None: for attr in should_match: if getattr(self.var_info, attr) != getattr(self, attr): @@ -212,7 +218,7 @@ def from_varinfo(cls, var_info: VarInfo) -> "ExprInfo": location=var_info.location, is_compile_time_constant=var_info.is_compile_time_constant, is_runtime_constant=var_info.is_runtime_constant, - is_immutable=var_info.is_immutable + is_immutable=var_info.is_immutable, ) def copy_with_type(self, typ: VyperType) -> "ExprInfo": diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 53dbb40178..4ef0d9a6e8 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -193,7 +193,12 @@ def __init__( (DataLocation.MEMORY, False) if self.func.is_internal else (DataLocation.CALLDATA, True) ) for arg in self.func.arguments: - namespace[arg.name] = VarInfo(arg.typ, location=location, is_immutable=is_immutable, is_runtime_constant=is_immutable) + namespace[arg.name] = VarInfo( + arg.typ, + location=location, + is_immutable=is_immutable, + is_runtime_constant=is_immutable, + ) for node in fn_node.body: self.visit(node) @@ -473,7 +478,9 @@ def visit_For(self, node): with self.namespace.enter_scope(): try: - self.namespace[iter_name] = VarInfo(possible_target_type, is_compile_time_constant=True) + self.namespace[iter_name] = VarInfo( + possible_target_type, is_compile_time_constant=True + ) except VyperException as exc: raise exc.with_annotation(node) from None diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 237792ef03..b3a1297a38 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -91,11 +91,18 @@ def get_expr_info(self, node: vy_ast.VyperNode) -> ExprInfo: # kludge! for validate_modification in local analysis of Assign types = [self.get_expr_info(n) for n in node.elements] location = sorted((i.location for i in types), key=lambda k: k.value)[-1] - is_constant = any((getattr(i, "is_constant", False) for i in types)) + is_compile_time_constant = any( + (getattr(i, "is_compile_time_constant", False) for i in types) + ) + is_runtime_constant = any((getattr(i, "is_runtime_constant", False) for i in types)) is_immutable = any((getattr(i, "is_immutable", False) for i in types)) return ExprInfo( - t, location=location, is_constant=is_constant, is_immutable=is_immutable + t, + location=location, + is_compile_time_constant=is_compile_time_constant, + is_runtime_constant=is_runtime_constant, + is_immutable=is_immutable, ) # If it's a Subscript, propagate the subscriptable varinfo From 0f0ecd10e59d8da2ccabeceede20630185652365 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 10:13:02 +0800 Subject: [PATCH 012/120] set folded_value metadata in constant node ctor --- vyper/ast/nodes.py | 8 ++++++++ vyper/semantics/analysis/pre_typecheck.py | 3 --- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index ca0c986fe0..a0ee8d6190 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -751,6 +751,10 @@ class Constant(ExprNode): # inherited class for all simple constant node types __slots__ = ("value",) + def __init__(self, parent: Optional["VyperNode"] = None, **kwargs: dict): + super().__init__(parent, **kwargs) + self._metadata["folded_value"] = self + class Num(Constant): # inherited class for all numeric constant node types @@ -904,6 +908,10 @@ class Dict(ExprNode): class NameConstant(Constant): __slots__ = ("value",) + def __init__(self, parent: Optional["VyperNode"] = None, **kwargs: dict): + super().__init__(parent, **kwargs) + self._metadata["folded_value"] = self + class Name(ExprNode): __slots__ = ("id",) diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index d175604e3f..9544ccc765 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -53,9 +53,6 @@ def prefold(node: vy_ast.VyperNode, constants: dict) -> None: if isinstance(node, vy_ast.UnaryOp): node._metadata["folded_value"] = node.prefold() - if isinstance(node, (vy_ast.Constant, vy_ast.NameConstant)): - node._metadata["folded_value"] = node - if isinstance(node, vy_ast.Compare): node._metadata["folded_value"] = node.prefold() From e9be2811ab4c8c103c3562a48936767303924708 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 10:19:26 +0800 Subject: [PATCH 013/120] fix for loop bound --- vyper/semantics/analysis/local.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 4ef0d9a6e8..cc609bebe8 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -357,22 +357,24 @@ def visit_For(self, node): kwargs = {s.arg: s.value for s in range_.keywords or []} if len(args) == 1: # range(CONSTANT) - n = args[0]._metadata.get("folded_value") + n = args[0] bound = kwargs.pop("bound", None) validate_expected_type(n, IntegerT.any()) if bound is None: - if not isinstance(n, vy_ast.Num): + n_val = n._metadata.get("folded_value") + if not isinstance(n_val, vy_ast.Num): raise StateAccessViolation("Value must be a literal", n) - if n.value <= 0: + if n_val.value <= 0: raise StructureException("For loop must have at least 1 iteration", args[0]) type_list = get_possible_types_from_node(n) else: - if not isinstance(bound, vy_ast.Num): + bound_val = bound._metadata.get("folded_value") + if not isinstance(bound_val, vy_ast.Num): raise StateAccessViolation("bound must be a literal", bound) - if bound.value <= 0: - raise StructureException("bound must be at least 1", args[0]) + if bound_val.value <= 0: + raise StructureException("bound must be at least 1", bound) type_list = get_common_types(n, bound) else: From 94f4f88b0c537d34305d3300da4c0d30b80d6de1 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:34:56 +0800 Subject: [PATCH 014/120] wip --- vyper/ast/folding.py | 1 + vyper/ast/nodes.py | 43 +++++++++++++++++------ vyper/builtins/functions.py | 10 +++--- vyper/semantics/analysis/local.py | 2 +- vyper/semantics/analysis/pre_typecheck.py | 16 ++++++--- vyper/semantics/analysis/utils.py | 2 +- 6 files changed, 53 insertions(+), 21 deletions(-) diff --git a/vyper/ast/folding.py b/vyper/ast/folding.py index 17ae0a9da7..598eafbdb6 100644 --- a/vyper/ast/folding.py +++ b/vyper/ast/folding.py @@ -52,6 +52,7 @@ def replace_literal_ops(vyper_module: vy_ast.Module) -> int: # e.g. DynArray[uint256, 2 ** 8] typ = node._metadata.get("type") if typ: + vy_ast._validate_numeric_bounds(node, new_node.value) new_node._metadata["type"] = node._metadata["type"] changed_nodes += 1 diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index a0ee8d6190..fe4b841aea 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -373,6 +373,9 @@ def description(self): """ return getattr(self, "_description", type(self).__name__) + def prefold(self) -> "VyperNode": + return + def evaluate(self) -> "VyperNode": """ Attempt to evaluate the content of a node and generate a new node from it. @@ -891,6 +894,13 @@ class List(ExprNode): __slots__ = ("elements",) _translated_fields = {"elts": "elements"} + def prefold(self): + for e in self.elements: + e.prefold() + elements = [e._metadata.get("folded_value") for e in self.elements] + if None not in elements: + self._metadata["folded_value"] = type(self).from_node(self, elements=elements) + class Tuple(ExprNode): __slots__ = ("elements",) @@ -921,12 +931,13 @@ class UnaryOp(ExprNode): __slots__ = ("op", "operand") def prefold(self) -> ExprNode: + self.operand.prefold() operand = self.operand._metadata.get("folded_value") if operand is None: return value = self.op._op(operand.value) - return type(self.operand).from_node(self, value=value) + self._metadata["folded_value"] = type(self.operand).from_node(self, value=value) def evaluate(self) -> ExprNode: """ @@ -945,7 +956,6 @@ def evaluate(self) -> ExprNode: raise UnfoldableNode("Node contains invalid field(s) for evaluation") value = self.op._op(self.operand.value) - _validate_numeric_bounds(self, value) return type(self.operand).from_node(self, value=value) @@ -977,20 +987,20 @@ class BinOp(ExprNode): __slots__ = ("left", "op", "right") def prefold(self) -> ExprNode: + self.left.prefold() + self.right.prefold() left = self.left._metadata.get("folded_value") right = self.right._metadata.get("folded_value") - if None in (left, right): - return - # this validation is performed to prevent the compiler from hanging # on very large shifts and improve the error message for negative # values. if isinstance(self.op, (LShift, RShift)) and not (0 <= right.value <= 256): raise InvalidLiteral("Shift bits must be between 0 and 256", right) - value = self.op._op(left.value, right.value) - return type(left).from_node(self, value=value) + if isinstance(left, type(right)) and isinstance(left, (Int, Decimal)): + value = self.op._op(left.value, right.value) + self._metadata["folded_value"] = type(left).from_node(self, value=value) def evaluate(self) -> ExprNode: """ @@ -1014,7 +1024,6 @@ def evaluate(self) -> ExprNode: raise InvalidLiteral("Shift bits must be between 0 and 256", right) value = self.op._op(left.value, right.value) - _validate_numeric_bounds(self, value) return type(left).from_node(self, value=value) @@ -1143,12 +1152,14 @@ class BoolOp(ExprNode): __slots__ = ("op", "values") def prefold(self) -> ExprNode: + for i in self.values: + i.prefold() values = [i._metadata.get("folded_value") for i in self.values] if None in values: return value = self.op._op(values) - return NameConstant.from_node(self, value=value) + self._metadata["folded_value"] = NameConstant.from_node(self, value=value) def evaluate(self) -> ExprNode: """ @@ -1207,6 +1218,8 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def prefold(self) -> ExprNode: + self.left.prefold() + self.right.prefold() left = self.left._metadata.get("folded_value") right = self.right._metadata.get("folded_value") @@ -1214,7 +1227,7 @@ def prefold(self) -> ExprNode: return value = self.op._op(left.value, right.value) - return NameConstant.from_node(self, value=value) + self._metadata["folded_value"] = NameConstant.from_node(self, value=value) def evaluate(self) -> ExprNode: """ @@ -1318,6 +1331,16 @@ class Attribute(ExprNode): class Subscript(ExprNode): __slots__ = ("slice", "value") + def prefold(self): + self.slice.value.prefold() + self.value.prefold() + + slice_ = self.slice.value._metadata.get("folded_value") + value = self.value._metadata.get("folded_value") + + if None not in (slice_, value): + self._metadata["folded_value"] = value.elements[slice_.value] + def evaluate(self) -> ExprNode: """ Attempt to evaluate the subscript. diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 04b87d97d9..b92efa96a0 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -142,10 +142,11 @@ class Floor(BuiltinFunction): def evaluate(self, node): validate_call_args(node, 1) - if not isinstance(node.args[0], vy_ast.Decimal): + value = node.args[0]._metadata.get("folded_value") + if not isinstance(value, vy_ast.Decimal): raise UnfoldableNode - value = math.floor(node.args[0].value) + value = math.floor(value.value) return vy_ast.Int.from_node(node, value=value) @process_inputs @@ -172,10 +173,11 @@ class Ceil(BuiltinFunction): def evaluate(self, node): validate_call_args(node, 1) - if not isinstance(node.args[0], vy_ast.Decimal): + value = node.args[0]._metadata.get("folded_value") + if not isinstance(value, vy_ast.Decimal): raise UnfoldableNode - value = math.ceil(node.args[0].value) + value = math.ceil(value.value) return vy_ast.Int.from_node(node, value=value) @process_inputs diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index cc609bebe8..1e24c3c623 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -757,7 +757,7 @@ def visit_Subscript(self, node: vy_ast.Subscript, typ: VyperType) -> None: # don't recurse; can't annotate AST children of type definition return - if isinstance(node.value, vy_ast.List): + if isinstance(node.value, (vy_ast.List, vy_ast.Subscript)): possible_base_types = get_possible_types_from_node(node.value) for possible_type in possible_base_types: diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index 9544ccc765..425bf9d57e 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -48,16 +48,22 @@ def pre_typecheck(node: vy_ast.Module): def prefold(node: vy_ast.VyperNode, constants: dict) -> None: if isinstance(node, vy_ast.BinOp): - node._metadata["folded_value"] = node.prefold() + node.prefold() if isinstance(node, vy_ast.UnaryOp): - node._metadata["folded_value"] = node.prefold() + node.prefold() if isinstance(node, vy_ast.Compare): - node._metadata["folded_value"] = node.prefold() + node.prefold() if isinstance(node, vy_ast.BoolOp): - node._metadata["folded_value"] = node.prefold() + node.prefold() + + if isinstance(node, vy_ast.Subscript): + node.prefold() + + if isinstance(node, vy_ast.List): + node.prefold() if isinstance(node, vy_ast.Name): var_name = node.id @@ -71,7 +77,7 @@ def prefold(node: vy_ast.VyperNode, constants: dict) -> None: func_name = node.func.id call_type = DISPATCH_TABLE.get(func_name) - if call_type and getattr(call_type, "_is_folded_before_codegen", False): + if call_type and hasattr(call_type, "evaluate"): try: node._metadata["folded_value"] = call_type.evaluate(node) # type: ignore except UnfoldableNode: diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index b3a1297a38..baa3aa67c2 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -376,7 +376,7 @@ def types_from_Name(self, node): def types_from_Subscript(self, node): # index access, e.g. `foo[1]` - if isinstance(node.value, vy_ast.List): + if isinstance(node.value, (vy_ast.List, vy_ast.Subscript)): types_list = self.get_possible_types_from_node(node.value) ret = [] for t in types_list: From 3340cd10a1c4bd27c17c0a92c4ae621a35cdb01d Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:35:01 +0800 Subject: [PATCH 015/120] fix folding tests --- tests/unit/ast/test_folding.py | 423 ++++++++++++++++++++++++++------- 1 file changed, 340 insertions(+), 83 deletions(-) diff --git a/tests/unit/ast/test_folding.py b/tests/unit/ast/test_folding.py index 62a7140e97..d0aff945c9 100644 --- a/tests/unit/ast/test_folding.py +++ b/tests/unit/ast/test_folding.py @@ -3,99 +3,243 @@ from vyper import ast as vy_ast from vyper.ast import folding from vyper.exceptions import OverflowException +from vyper.semantics import validate_semantics def test_integration(): - test_ast = vy_ast.parse_to_ast("[1+2, 6+7][8-8]") - expected_ast = vy_ast.parse_to_ast("3") + test = """ +@external +def foo(): + a: uint256 = [1+2, 6+7][8-8] + """ + + expected = """ +@external +def foo(): + a: uint256 = 3 + """ + test_ast = vy_ast.parse_to_ast(test) + expected_ast = vy_ast.parse_to_ast(expected) + + validate_semantics(test_ast, {}) folding.fold(test_ast) assert vy_ast.compare_nodes(test_ast, expected_ast) def test_replace_binop_simple(): - test_ast = vy_ast.parse_to_ast("1 + 2") - expected_ast = vy_ast.parse_to_ast("3") + test = """ +@external +def foo(): + a: uint256 = 1 + 2 + """ + expected = """ +@external +def foo(): + a: uint256 = 3 + """ + + test_ast = vy_ast.parse_to_ast(test) + expected_ast = vy_ast.parse_to_ast(expected) + + validate_semantics(test_ast, {}) folding.replace_literal_ops(test_ast) assert vy_ast.compare_nodes(test_ast, expected_ast) def test_replace_binop_nested(): - test_ast = vy_ast.parse_to_ast("((6 + (2**4)) * 4) / 2") - expected_ast = vy_ast.parse_to_ast("44") + test = """ +@external +def foo(): + a: uint256 = ((6 + (2**4)) * 4) / 2 + """ + + expected = """ +@external +def foo(): + a: uint256 = 44 + """ + test_ast = vy_ast.parse_to_ast(test) + expected_ast = vy_ast.parse_to_ast(expected) + validate_semantics(test_ast, {}) folding.replace_literal_ops(test_ast) assert vy_ast.compare_nodes(test_ast, expected_ast) def test_replace_binop_nested_intermediate_overflow(): - test_ast = vy_ast.parse_to_ast("2**255 * 2 / 10") + test = """ +@external +def foo(): + a: uint256 = 2**255 * 2 / 10 + """ + test_ast = vy_ast.parse_to_ast(test) + validate_semantics(test_ast, {}) with pytest.raises(OverflowException): - folding.fold(test_ast) + folding.replace_literal_ops(test_ast) def test_replace_binop_nested_intermediate_underflow(): - test_ast = vy_ast.parse_to_ast("-2**255 * 2 - 10 + 100") + test = """ +@external +def foo(): + a: int256 = -2**255 * 2 - 10 + 100 + """ + test_ast = vy_ast.parse_to_ast(test) + validate_semantics(test_ast, {}) with pytest.raises(OverflowException): - folding.fold(test_ast) + folding.replace_literal_ops(test_ast) def test_replace_decimal_nested_intermediate_overflow(): - test_ast = vy_ast.parse_to_ast( - "18707220957835557353007165858768422651595.9365500927 + 1e-10 - 1e-10" - ) + test = """ +@external +def foo(): + a: decimal = 18707220957835557353007165858768422651595.9365500927 + 1e-10 - 1e-10 + """ + test_ast = vy_ast.parse_to_ast(test) + validate_semantics(test_ast, {}) with pytest.raises(OverflowException): - folding.fold(test_ast) + folding.replace_literal_ops(test_ast) def test_replace_decimal_nested_intermediate_underflow(): - test_ast = vy_ast.parse_to_ast( - "-18707220957835557353007165858768422651595.9365500928 - 1e-10 + 1e-10" - ) + test = """ +@external +def foo(): + a: decimal = -18707220957835557353007165858768422651595.9365500928 - 1e-10 + 1e-10 + """ + test_ast = vy_ast.parse_to_ast(test) + validate_semantics(test_ast, {}) with pytest.raises(OverflowException): - folding.fold(test_ast) + folding.replace_literal_ops(test_ast) def test_replace_literal_ops(): - test_ast = vy_ast.parse_to_ast("[not True, True and False, True or False]") - expected_ast = vy_ast.parse_to_ast("[False, False, True]") + test = """ +@external +def foo(): + a: bool[3] = [not True, True and False, True or False] + """ + + expected = """ +@external +def foo(): + a: bool[3] = [False, False, True] + """ + test_ast = vy_ast.parse_to_ast(test) + expected_ast = vy_ast.parse_to_ast(expected) + validate_semantics(test_ast, {}) folding.replace_literal_ops(test_ast) assert vy_ast.compare_nodes(test_ast, expected_ast) def test_replace_subscripts_simple(): - test_ast = vy_ast.parse_to_ast("[foo, bar, baz][1]") - expected_ast = vy_ast.parse_to_ast("bar") + test = """ +@external +def foo(): + a: uint256 = [1, 2, 3][1] + """ + + expected = """ +@external +def foo(): + a: uint256 = 2 + """ + test_ast = vy_ast.parse_to_ast(test) + expected_ast = vy_ast.parse_to_ast(expected) + validate_semantics(test_ast, {}) folding.replace_subscripts(test_ast) assert vy_ast.compare_nodes(test_ast, expected_ast) def test_replace_subscripts_nested(): - test_ast = vy_ast.parse_to_ast("[[0, 1], [2, 3], [4, 5]][2][1]") - expected_ast = vy_ast.parse_to_ast("5") + test = """ +@external +def foo(): + a: uint256 = [[0, 1], [2, 3], [4, 5]][2][1] + """ + + expected = """ +@external +def foo(): + a: uint256 = 5 + """ + test_ast = vy_ast.parse_to_ast(test) + expected_ast = vy_ast.parse_to_ast(expected) + validate_semantics(test_ast, {}) folding.replace_subscripts(test_ast) assert vy_ast.compare_nodes(test_ast, expected_ast) constants_modified = [ - "bar = FOO", - "bar: int128[FOO]", - "[1, 2, FOO]", - "def bar(a: int128 = FOO): pass", - "log bar(FOO)", - "FOO + 1", - "a: int128[FOO / 2]", - "a[FOO - 1] = 44", + """ +FOO: constant(uint256) = 4 + +@external +def foo(): + bar: uint256 = 1 + bar = FOO + """, + """ +FOO: constant(uint256) = 4 +bar: int128[FOO] + """, + """ +FOO: constant(uint256) = 4 + +@external +def foo(): + a: uint256[3] = [1, 2, FOO] + """, + """ +FOO: constant(uint256) = 4 +@external +def bar(a: uint256 = FOO): + pass + """, + """ +FOO: constant(uint256) = 4 + +event bar: + a: uint256 + +@external +def foo(): + log bar(FOO) + """, + """ +FOO: constant(uint256) = 4 + +@external +def foo(): + a: uint256 = FOO + 1 + """, + """ +FOO: constant(uint256) = 4 + +@external +def foo(): + a: int128[FOO / 2] = [1, 2] + """, + """ +FOO: constant(uint256) = 4 + +@external +def bar(x: DynArray[uint256, 4]): + a: DynArray[uint256, 4] = x + a[FOO - 1] = 44 + """, ] @@ -104,83 +248,156 @@ def test_replace_constant(source): unmodified_ast = vy_ast.parse_to_ast(source) folded_ast = vy_ast.parse_to_ast(source) - folding.replace_constant(folded_ast, "FOO", vy_ast.Int(value=31337), True) + validate_semantics(folded_ast, {}) + folding.replace_user_defined_constants(folded_ast) assert not vy_ast.compare_nodes(unmodified_ast, folded_ast) constants_unmodified = [ - "FOO = 42", - "self.FOO = 42", - "bar = FOO()", - "FOO()", - "bar = FOO()", - "bar = self.FOO", - "log FOO(bar)", - "[1, 2, FOO()]", - "FOO[42] = 2", -] + """ +FOO: immutable(uint256) +@external +def __init__(): + FOO = 42 + """, + """ +FOO: uint256 -@pytest.mark.parametrize("source", constants_unmodified) -def test_replace_constant_no(source): - unmodified_ast = vy_ast.parse_to_ast(source) - folded_ast = vy_ast.parse_to_ast(source) +@external +def foo(): + self.FOO = 42 + """, + """ +bar: uint256 - folding.replace_constant(folded_ast, "FOO", vy_ast.Int(value=31337), True) +@internal +def FOO() -> uint256: + return 123 - assert vy_ast.compare_nodes(unmodified_ast, folded_ast) +@external +def foo(): + bar: uint256 = 456 + bar = self.FOO() + """, + """ +@internal +def FOO(): + pass + +@external +def foo(): + self.FOO() + """, + """ +FOO: uint256 +@external +def foo(): + bar: uint256 = 1 + bar = self.FOO + """, + """ +event FOO: + a: uint256 -userdefined_modified = [ - "FOO", - "foo = FOO", - "foo: int128[FOO] = 42", - "foo = [FOO]", - "foo += FOO", - "def foo(bar: int128 = FOO): pass", - "def foo(): bar = FOO", - "def foo(): return FOO", +@external +def foo(bar: uint256): + log FOO(bar) + """, + """ +@internal +def FOO() -> uint256: + return 3 + +@external +def foo(): + a: uint256[3] = [1, 2, self.FOO()] + """, + """ +@external +def foo(): + FOO: DynArray[uint256, 5] = [1, 2, 3, 4, 5] + FOO[4] = 2 + """, ] -@pytest.mark.parametrize("source", userdefined_modified) -def test_replace_userdefined_constant(source): - source = f"FOO: constant(int128) = 42\n{source}" - +@pytest.mark.parametrize("source", constants_unmodified) +def test_replace_constant_no(source): unmodified_ast = vy_ast.parse_to_ast(source) folded_ast = vy_ast.parse_to_ast(source) + validate_semantics(folded_ast, {}) folding.replace_user_defined_constants(folded_ast) - assert not vy_ast.compare_nodes(unmodified_ast, folded_ast) + assert vy_ast.compare_nodes(unmodified_ast, folded_ast) -userdefined_unmodified = [ - "FOO: constant(int128) = 42", - "FOO = 42", - "FOO += 42", - "FOO()", - "def foo(FOO: int128 = 42): pass", - "def foo(): FOO = 42", - "def FOO(): pass", +userdefined_modified = [ + """ +@external +def foo(): + foo: int128 = FOO + """, + """ +@external +def foo(): + foo: DynArray[int128, FOO] = [] + """, + """ +@external +def foo(): + foo: int128[1] = [FOO] + """, + """ +@external +def foo(): + foo: int128 = 3 + foo += FOO + """, + """ +@external +def foo(bar: int128 = FOO): + pass + """, + """ +@external +def foo() -> int128: + return FOO + """, ] -@pytest.mark.parametrize("source", userdefined_unmodified) -def test_replace_userdefined_constant_no(source): +@pytest.mark.parametrize("source", userdefined_modified) +def test_replace_userdefined_constant(source): source = f"FOO: constant(int128) = 42\n{source}" unmodified_ast = vy_ast.parse_to_ast(source) folded_ast = vy_ast.parse_to_ast(source) + validate_semantics(folded_ast, {}) folding.replace_user_defined_constants(folded_ast) - assert vy_ast.compare_nodes(unmodified_ast, folded_ast) + assert not vy_ast.compare_nodes(unmodified_ast, folded_ast) dummy_address = "0x000000000000000000000000000000000000dEaD" -userdefined_attributes = [("b: uint256 = ADDR.balance", f"b: uint256 = {dummy_address}.balance")] +userdefined_attributes = [ + ( + """ +@external +def foo(): + b: uint256 = ADDR.balance + """, + f""" +@external +def foo(): + b: uint256 = {dummy_address}.balance + """, + ) +] @pytest.mark.parametrize("source", userdefined_attributes) @@ -190,6 +407,7 @@ def test_replace_userdefined_attribute(source): r_source = f"{preamble}\n{source[1]}" l_ast = vy_ast.parse_to_ast(l_source) + validate_semantics(l_ast, {}) folding.replace_user_defined_constants(l_ast) r_ast = vy_ast.parse_to_ast(r_source) @@ -197,7 +415,20 @@ def test_replace_userdefined_attribute(source): assert vy_ast.compare_nodes(l_ast, r_ast) -userdefined_struct = [("b: Foo = FOO", "b: Foo = Foo({a: 123, b: 456})")] +userdefined_struct = [ + ( + """ +@external +def foo(): + b: Foo = FOO + """, + """ +@external +def foo(): + b: Foo = Foo({a: 123, b: 456}) + """, + ) +] @pytest.mark.parametrize("source", userdefined_struct) @@ -213,6 +444,7 @@ def test_replace_userdefined_struct(source): r_source = f"{preamble}\n{source[1]}" l_ast = vy_ast.parse_to_ast(l_source) + validate_semantics(l_ast, {}) folding.replace_user_defined_constants(l_ast) r_ast = vy_ast.parse_to_ast(r_source) @@ -221,7 +453,18 @@ def test_replace_userdefined_struct(source): userdefined_nested_struct = [ - ("b: Foo = FOO", "b: Foo = Foo({f1: Bar({b1: 123, b2: 456}), f2: 789})") + ( + """ +@external +def foo(): + b: Foo = FOO + """, + """ +@external +def foo(): + b: Foo = Foo({f1: Bar({b1: 123, b2: 456}), f2: 789}) + """, + ) ] @@ -242,6 +485,7 @@ def test_replace_userdefined_nested_struct(source): r_source = f"{preamble}\n{source[1]}" l_ast = vy_ast.parse_to_ast(l_source) + validate_semantics(l_ast, {}) folding.replace_user_defined_constants(l_ast) r_ast = vy_ast.parse_to_ast(r_source) @@ -252,12 +496,24 @@ def test_replace_userdefined_nested_struct(source): builtin_folding_functions = [("ceil(4.2)", "5"), ("floor(4.2)", "4")] builtin_folding_sources = [ - "{}", - "foo = {}", - "foo = [{0}, {0}]", - "def foo(): {}", - "def foo(): return {}", - "def foo(bar: {}): pass", + """ +@external +def foo(): + foo: int256 = {} + """, + """ +foo: constant(int256[2]) = [{0}, {0}] + """, + """ +@external +def foo() -> int256: + return {} + """, + """ +@external +def foo(bar: int256 = {}): + pass + """, ] @@ -267,6 +523,7 @@ def test_replace_builtins(source, original, result): original_ast = vy_ast.parse_to_ast(source.format(original)) target_ast = vy_ast.parse_to_ast(source.format(result)) + validate_semantics(original_ast, {}) folding.replace_builtin_functions(original_ast) assert vy_ast.compare_nodes(original_ast, target_ast) From 763ab84ce726d9bcaeec34169361196945ff703e Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:38:57 +0800 Subject: [PATCH 016/120] fix darray from annotation --- vyper/semantics/types/subscriptable.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/vyper/semantics/types/subscriptable.py b/vyper/semantics/types/subscriptable.py index db9c9b6f0f..f6eadf4b4e 100644 --- a/vyper/semantics/types/subscriptable.py +++ b/vyper/semantics/types/subscriptable.py @@ -274,12 +274,10 @@ def compare_type(self, other): @classmethod def from_annotation(cls, node: vy_ast.Subscript) -> "DArrayT": - length = node.slice.value.elements[1]._metadata.get("folded_value") if ( not isinstance(node, vy_ast.Subscript) or not isinstance(node.slice, vy_ast.Index) or not isinstance(node.slice.value, vy_ast.Tuple) - or not isinstance(length, vy_ast.Int) or len(node.slice.value.elements) != 2 ): raise StructureException( @@ -287,8 +285,16 @@ def from_annotation(cls, node: vy_ast.Subscript) -> "DArrayT": node, ) + length = node.slice.value.elements[1]._metadata.get("folded_value") + if not isinstance(length, vy_ast.Int): + raise StructureException( + "DynArray must have a max length of integer type, e.g. DynArray[bool, 5]", node + ) + value_type = type_from_annotation(node.slice.value.elements[0]) if not value_type._as_darray: + # TODO: this is currently not reachable because all instantiable types are set to True + # and non-instantiable types like events are caught by `type_from_annotation` raise StructureException(f"Arrays of {value_type} are not allowed", node) max_length = length.value From 56b4149da1af7eba5a6142cb0016ea3d088419e7 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:53:19 +0800 Subject: [PATCH 017/120] fix raw call kwargs --- vyper/builtins/functions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index b92efa96a0..6eda338d73 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -1081,7 +1081,11 @@ def fetch_call_return(self, node): kwargz = {i.arg: i.value for i in node.keywords} outsize = kwargz.get("max_outsize") + if outsize is not None: + outsize = outsize._metadata.get("folded_value") revert_on_failure = kwargz.get("revert_on_failure") + if revert_on_failure is not None: + revert_on_failure = revert_on_failure._metadata.get("folded_value") revert_on_failure = revert_on_failure.value if revert_on_failure is not None else True if outsize is None or outsize.value == 0: From 7c4fe7b992c001f17eeb85acecc925e42802224b Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:53:27 +0800 Subject: [PATCH 018/120] fix constant expression visit --- vyper/semantics/analysis/module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index ecfff33a46..c7f3dca671 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -233,13 +233,13 @@ def _validate_self_namespace(): raise exc.with_annotation(node) from None if node.is_constant: - ExprVisitor().visit(node.value, type_) if not node.value: raise VariableDeclarationException("Constant must be declared with a value", node) if not check_constant(node.value): raise StateAccessViolation("Value must be a literal", node.value) validate_expected_type(node.value, type_) + ExprVisitor().visit(node.value, type_) _validate_self_namespace() return _finalize() From e549ce27ab83e13193bf1a0bb0419f060b11ef3a Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:16:44 +0800 Subject: [PATCH 019/120] fix unary op --- vyper/ast/nodes.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index fe4b841aea..b3a441e90e 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -933,11 +933,9 @@ class UnaryOp(ExprNode): def prefold(self) -> ExprNode: self.operand.prefold() operand = self.operand._metadata.get("folded_value") - if operand is None: - return - - value = self.op._op(operand.value) - self._metadata["folded_value"] = type(self.operand).from_node(self, value=value) + if operand is not None: + value = self.op._op(operand.value) + self._metadata["folded_value"] = type(operand).from_node(self, value=value) def evaluate(self) -> ExprNode: """ @@ -1155,11 +1153,9 @@ def prefold(self) -> ExprNode: for i in self.values: i.prefold() values = [i._metadata.get("folded_value") for i in self.values] - if None in values: - return - - value = self.op._op(values) - self._metadata["folded_value"] = NameConstant.from_node(self, value=value) + if None not in values: + value = self.op._op(values) + self._metadata["folded_value"] = NameConstant.from_node(self, value=value) def evaluate(self) -> ExprNode: """ @@ -1223,11 +1219,9 @@ def prefold(self) -> ExprNode: left = self.left._metadata.get("folded_value") right = self.right._metadata.get("folded_value") - if None in (left, right): - return - - value = self.op._op(left.value, right.value) - self._metadata["folded_value"] = NameConstant.from_node(self, value=value) + if None not in (left, right): + value = self.op._op(left.value, right.value) + self._metadata["folded_value"] = NameConstant.from_node(self, value=value) def evaluate(self) -> ExprNode: """ From 7409e117c0acbeca93a192c005d1a0b3d3eaf1d5 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:17:00 +0800 Subject: [PATCH 020/120] fix exception --- vyper/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/exceptions.py b/vyper/exceptions.py index 3bde20356e..c579c0cf49 100644 --- a/vyper/exceptions.py +++ b/vyper/exceptions.py @@ -105,7 +105,7 @@ def __str__(self): if isinstance(node, vy_ast.VyperNode): module_node = node.get_ancestor(vy_ast.Module) - if module_node.get("name") not in (None, ""): + if module_node and module_node.get("name") not in (None, ""): node_msg = f'{node_msg}contract "{module_node.name}:{node.lineno}", ' fn_node = node.get_ancestor(vy_ast.FunctionDef) From a89b14b3602ff5240354c9859fd156a70cc3117e Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:17:12 +0800 Subject: [PATCH 021/120] revert folding tests --- tests/unit/ast/test_folding.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/unit/ast/test_folding.py b/tests/unit/ast/test_folding.py index d0aff945c9..6564da1c3b 100644 --- a/tests/unit/ast/test_folding.py +++ b/tests/unit/ast/test_folding.py @@ -2,7 +2,7 @@ from vyper import ast as vy_ast from vyper.ast import folding -from vyper.exceptions import OverflowException +from vyper.exceptions import InvalidType, OverflowException from vyper.semantics import validate_semantics @@ -78,9 +78,8 @@ def foo(): a: uint256 = 2**255 * 2 / 10 """ test_ast = vy_ast.parse_to_ast(test) - validate_semantics(test_ast, {}) with pytest.raises(OverflowException): - folding.replace_literal_ops(test_ast) + validate_semantics(test_ast, {}) def test_replace_binop_nested_intermediate_underflow(): @@ -90,9 +89,8 @@ def foo(): a: int256 = -2**255 * 2 - 10 + 100 """ test_ast = vy_ast.parse_to_ast(test) - validate_semantics(test_ast, {}) - with pytest.raises(OverflowException): - folding.replace_literal_ops(test_ast) + with pytest.raises(InvalidType): + validate_semantics(test_ast, {}) def test_replace_decimal_nested_intermediate_overflow(): @@ -102,9 +100,8 @@ def foo(): a: decimal = 18707220957835557353007165858768422651595.9365500927 + 1e-10 - 1e-10 """ test_ast = vy_ast.parse_to_ast(test) - validate_semantics(test_ast, {}) with pytest.raises(OverflowException): - folding.replace_literal_ops(test_ast) + validate_semantics(test_ast, {}) def test_replace_decimal_nested_intermediate_underflow(): @@ -114,9 +111,8 @@ def foo(): a: decimal = -18707220957835557353007165858768422651595.9365500928 - 1e-10 + 1e-10 """ test_ast = vy_ast.parse_to_ast(test) - validate_semantics(test_ast, {}) with pytest.raises(OverflowException): - folding.replace_literal_ops(test_ast) + validate_semantics(test_ast, {}) def test_replace_literal_ops(): From 6d0f71414949b26067418d0a8d10c5cb5bc179da Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:18:46 +0800 Subject: [PATCH 022/120] remove literal bounds validation --- vyper/ast/nodes.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index b3a441e90e..b49647d9f8 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -193,23 +193,6 @@ def _raise_syntax_exc(error_msg: str, ast_struct: dict) -> None: ) -def _validate_numeric_bounds( - node: Union["BinOp", "UnaryOp"], value: Union[decimal.Decimal, int] -) -> None: - if isinstance(value, decimal.Decimal): - # this will change if/when we add more decimal types - lower, upper = SizeLimits.MIN_AST_DECIMAL, SizeLimits.MAX_AST_DECIMAL - elif isinstance(value, int): - lower, upper = SizeLimits.MIN_INT256, SizeLimits.MAX_UINT256 - else: - raise CompilerPanic(f"Unexpected return type from {node._op}: {type(value)}") - if not lower <= value <= upper: - raise OverflowException( - f"Result of {node.op.description} ({value}) is outside bounds of all numeric types", - node, - ) - - class VyperNode: """ Base class for all vyper AST nodes. From 7b38284667e26788ed39dee497d180cc3e136b15 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:18:58 +0800 Subject: [PATCH 023/120] validate folded value with typ --- vyper/semantics/analysis/local.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 1e24c3c623..3b50c33485 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -603,6 +603,11 @@ def visit(self, node, typ): # can happen. super().visit(node, typ) + folded_value = node._metadata.get("folded_value") + if folded_value: + #print("folded value: ", folded_value) + validate_expected_type(folded_value, typ) + # annotate node._metadata["type"] = typ From 78de745cbb29006295a03056b858eeb37d64e87a Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:19:24 +0800 Subject: [PATCH 024/120] revert literal validation in folding --- vyper/ast/folding.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vyper/ast/folding.py b/vyper/ast/folding.py index 598eafbdb6..17ae0a9da7 100644 --- a/vyper/ast/folding.py +++ b/vyper/ast/folding.py @@ -52,7 +52,6 @@ def replace_literal_ops(vyper_module: vy_ast.Module) -> int: # e.g. DynArray[uint256, 2 ** 8] typ = node._metadata.get("type") if typ: - vy_ast._validate_numeric_bounds(node, new_node.value) new_node._metadata["type"] = node._metadata["type"] changed_nodes += 1 From 519ec25050e7c604e369b43ec8772266d319352b Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:19:34 +0800 Subject: [PATCH 025/120] fix some tests --- tests/functional/codegen/types/numbers/test_constants.py | 4 ++-- tests/functional/syntax/test_bool.py | 2 +- tests/functional/syntax/test_ternary.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/functional/codegen/types/numbers/test_constants.py b/tests/functional/codegen/types/numbers/test_constants.py index 25617651ec..5f1a40d540 100644 --- a/tests/functional/codegen/types/numbers/test_constants.py +++ b/tests/functional/codegen/types/numbers/test_constants.py @@ -4,7 +4,7 @@ import pytest from vyper.compiler import compile_code -from vyper.exceptions import InvalidType +from vyper.exceptions import TypeMismatch from vyper.utils import MemoryPositions @@ -151,7 +151,7 @@ def test_custom_constants_fail(get_contract, assert_compile_failed, storage_type def foo() -> {return_type}: return MY_CONSTANT """ - assert_compile_failed(lambda: get_contract(code), InvalidType) + assert_compile_failed(lambda: get_contract(code), TypeMismatch) def test_constant_address(get_contract): diff --git a/tests/functional/syntax/test_bool.py b/tests/functional/syntax/test_bool.py index 48ed37321a..5388a92b95 100644 --- a/tests/functional/syntax/test_bool.py +++ b/tests/functional/syntax/test_bool.py @@ -37,7 +37,7 @@ def foo(): def foo() -> bool: return (1 == 2) <= (1 == 1) """, - TypeMismatch, + InvalidOperation, ), """ @external diff --git a/tests/functional/syntax/test_ternary.py b/tests/functional/syntax/test_ternary.py index 325be3e43b..3573648368 100644 --- a/tests/functional/syntax/test_ternary.py +++ b/tests/functional/syntax/test_ternary.py @@ -82,7 +82,7 @@ def foo() -> uint256: def foo() -> uint256: return 1 if TEST else 2 """, - InvalidType, + TypeMismatch, ), ( # bad test type: variable """ From 04ccfa6b01f828ed21b17a921fd2afafad1a1ad5 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:43:12 +0800 Subject: [PATCH 026/120] fix kwarg handler --- vyper/codegen/function_definitions/external_function.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vyper/codegen/function_definitions/external_function.py b/vyper/codegen/function_definitions/external_function.py index bd409012dc..f46aa6b2e5 100644 --- a/vyper/codegen/function_definitions/external_function.py +++ b/vyper/codegen/function_definitions/external_function.py @@ -63,7 +63,7 @@ def _generate_kwarg_handlers( # write default args to memory # goto external_function_common_ir - def handler_for(calldata_kwargs, folded_default_kwargs, original_default_kwargs): + def handler_for(calldata_kwargs, original_default_kwargs, folded_default_kwargs): calldata_args = func_t.positional_args + calldata_kwargs # create a fake type so that get_element_ptr works calldata_args_t = TupleT(list(arg.typ for arg in calldata_args)) @@ -128,10 +128,10 @@ def handler_for(calldata_kwargs, folded_default_kwargs, original_default_kwargs) # folded ast original_default_kwargs = keyword_args[i:] # unfolded ast - folded_default_kwargs = folded_keyword_args[1:] + folded_default_kwargs = folded_keyword_args[i:] sig, calldata_min_size, ir_node = handler_for( - calldata_kwargs, folded_default_kwargs, original_default_kwargs + calldata_kwargs, original_default_kwargs, folded_default_kwargs ) ret[sig] = calldata_min_size, ir_node From c10095b2d2a30a8f162ff07e7a4c537ce6630494 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:51:34 +0800 Subject: [PATCH 027/120] fix for semantics --- vyper/semantics/analysis/local.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 3b50c33485..f7b11210e2 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -387,7 +387,8 @@ def visit_For(self, node): validate_expected_type(args[0], IntegerT.any()) type_list = get_common_types(*args) - if not isinstance(args[0], vy_ast.Constant): + arg0_val = args[0]._metadata.get("folded_value") + if not isinstance(arg0_val, vy_ast.Constant): # range(x, x + CONSTANT) if not isinstance(args[1], vy_ast.BinOp) or not isinstance( args[1].op, vy_ast.Add @@ -409,10 +410,11 @@ def visit_For(self, node): ) else: # range(CONSTANT, CONSTANT) - if not isinstance(args[1], vy_ast.Int): + arg1_val = args[1]._metadata.get("folded_value") + if not isinstance(arg1_val, vy_ast.Int): raise InvalidType("Value must be a literal integer", args[1]) validate_expected_type(args[1], IntegerT.any()) - if args[0].value >= args[1].value: + if arg0_val.value >= arg1_val.value: raise StructureException("Second value must be > first value", args[1]) if not type_list: @@ -420,7 +422,8 @@ def visit_For(self, node): else: # iteration over a variable or literal list - if isinstance(node.iter, vy_ast.List) and len(node.iter.elements) == 0: + iter_val = node.iter._metadata.get("folded_value") + if isinstance(iter_val, vy_ast.List) and len(iter_val.elements) == 0: raise StructureException("For loop must have at least 1 iteration", node.iter) type_list = [ From e14da495b87e0b12a20ccb8c135dbbe3b3ae800d Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:51:40 +0800 Subject: [PATCH 028/120] fix iteration tests --- tests/functional/codegen/features/iteration/test_for_in_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/codegen/features/iteration/test_for_in_list.py b/tests/functional/codegen/features/iteration/test_for_in_list.py index fb01cc98eb..b634735d31 100644 --- a/tests/functional/codegen/features/iteration/test_for_in_list.py +++ b/tests/functional/codegen/features/iteration/test_for_in_list.py @@ -772,7 +772,7 @@ def test_for() -> int128: a = i return a """, - TypeMismatch, + InvalidType, ), ( """ From 9b94fdd7e8c36d6fa20a37fcacd987a7665658a1 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:51:49 +0800 Subject: [PATCH 029/120] fix lint --- vyper/semantics/analysis/local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index f7b11210e2..ce1ab983cf 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -608,7 +608,7 @@ def visit(self, node, typ): folded_value = node._metadata.get("folded_value") if folded_value: - #print("folded value: ", folded_value) + # print("folded value: ", folded_value) validate_expected_type(folded_value, typ) # annotate From e74d70166ebb2a5a5fcdb727658e71183dd772be Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 11:04:36 +0800 Subject: [PATCH 030/120] update tests --- .../functional/builtins/codegen/test_unary.py | 7 +----- tests/functional/builtins/folding/test_abs.py | 2 +- .../test_external_contract_calls.py | 2 +- tests/functional/syntax/test_unary.py | 22 +++++++++++++++++++ 4 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 tests/functional/syntax/test_unary.py diff --git a/tests/functional/builtins/codegen/test_unary.py b/tests/functional/builtins/codegen/test_unary.py index da3823edfe..826bbe9d0c 100644 --- a/tests/functional/builtins/codegen/test_unary.py +++ b/tests/functional/builtins/codegen/test_unary.py @@ -68,16 +68,11 @@ def bar() -> decimal: def test_negation_int128(get_contract): code = """ -a: constant(int128) = -2**127 - -@external -def foo() -> int128: - return -2**127 +a: constant(int128) = min_value(int128) @external def bar() -> int128: return -(a+1) """ c = get_contract(code) - assert c.foo() == -(2**127) assert c.bar() == 2**127 - 1 diff --git a/tests/functional/builtins/folding/test_abs.py b/tests/functional/builtins/folding/test_abs.py index 1c919d7826..629d5baf52 100644 --- a/tests/functional/builtins/folding/test_abs.py +++ b/tests/functional/builtins/folding/test_abs.py @@ -54,7 +54,7 @@ def test_abs_lower_bound_folded(get_contract, assert_tx_failed): source = """ @external def foo() -> int256: - return abs(-2**255) + return abs(min_value(int256)) """ with pytest.raises(OverflowException): get_contract(source) diff --git a/tests/functional/codegen/calling_convention/test_external_contract_calls.py b/tests/functional/codegen/calling_convention/test_external_contract_calls.py index 12fcde2f4f..935c7b74fc 100644 --- a/tests/functional/codegen/calling_convention/test_external_contract_calls.py +++ b/tests/functional/codegen/calling_convention/test_external_contract_calls.py @@ -380,7 +380,7 @@ def test_int128_too_long(get_contract, assert_tx_failed): contract_1 = """ @external def foo() -> int256: - return (2**255)-1 + return max_value(int256) """ c = get_contract(contract_1) diff --git a/tests/functional/syntax/test_unary.py b/tests/functional/syntax/test_unary.py new file mode 100644 index 0000000000..2b69f90023 --- /dev/null +++ b/tests/functional/syntax/test_unary.py @@ -0,0 +1,22 @@ +import pytest + +from vyper.compiler import compile_code +from vyper.exceptions import InvalidType, TypeMismatch + + +fail_list = [ + ( + """ +@external +def foo() -> int128: + return -2**127 + """, + InvalidType, + ), +] + + +@pytest.mark.parametrize("code,exc", fail_list) +def test_unary_fail(code, exc): + with pytest.raises(exc): + compile_code(code) From bbf05102721d46d7b1aa41438846e3b187a10416 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 11:24:14 +0800 Subject: [PATCH 031/120] fix minmax --- vyper/builtins/functions.py | 187 +++++++++++++++++----- vyper/semantics/analysis/local.py | 4 +- vyper/semantics/analysis/pre_typecheck.py | 2 +- vyper/semantics/analysis/utils.py | 2 + 4 files changed, 152 insertions(+), 43 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 6eda338d73..2a7d997d59 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -128,7 +128,7 @@ def fetch_call_return(self, node): type_ = self.infer_arg_types(node)[0].typedef return type_ - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): validate_call_args(node, 1) input_typedef = TYPE_T(type_from_annotation(node.args[0])) return [input_typedef] @@ -140,6 +140,9 @@ class Floor(BuiltinFunction): # TODO: maybe use int136? _return_type = INT256_T + def prefold(self, node): + return self.evaluate(node) + def evaluate(self, node): validate_call_args(node, 1) value = node.args[0]._metadata.get("folded_value") @@ -171,6 +174,9 @@ class Ceil(BuiltinFunction): # TODO: maybe use int136? _return_type = INT256_T + def prefold(self, node): + return self.evaluate(node) + def evaluate(self, node): validate_call_args(node, 1) value = node.args[0]._metadata.get("folded_value") @@ -206,7 +212,7 @@ def fetch_call_return(self, node): return target_typedef.typedef # TODO: push this down into convert.py for more consistency - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): validate_call_args(node, 2) target_type = type_from_annotation(node.args[1]) @@ -342,7 +348,7 @@ def fetch_call_return(self, node): return return_type - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) # return a concrete type for `b` b_type = get_possible_types_from_node(node.args[0]).pop() @@ -466,6 +472,9 @@ class Len(BuiltinFunction): _inputs = [("b", (StringT.any(), BytesT.any(), DArrayT.any()))] _return_type = UINT256_T + def prefold(self, node): + return self.evaluate(node) + def evaluate(self, node): validate_call_args(node, 1) arg = node.args[0] @@ -479,7 +488,7 @@ def evaluate(self, node): return vy_ast.Int.from_node(node, value=length) - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) # return a concrete type typ = get_possible_types_from_node(node.args[0]).pop() @@ -509,7 +518,7 @@ def fetch_call_return(self, node): return_type.set_length(length) return return_type - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): if len(node.args) < 2: raise ArgumentException("Invalid argument count: expected at least 2", node) @@ -603,6 +612,9 @@ class Keccak256(BuiltinFunction): _inputs = [("value", (BytesT.any(), BYTES32_T, StringT.any()))] _return_type = BYTES32_T + def prefold(self, node): + return self.evaluate(node) + def evaluate(self, node): validate_call_args(node, 1) if isinstance(node.args[0], vy_ast.Bytes): @@ -618,7 +630,7 @@ def evaluate(self, node): hash_ = f"0x{keccak256(value).hex()}" return vy_ast.Hex.from_node(node, value=hash_) - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) # return a concrete type for `value` value_type = get_possible_types_from_node(node.args[0]).pop() @@ -650,6 +662,9 @@ class Sha256(BuiltinFunction): _inputs = [("value", (BYTES32_T, BytesT.any(), StringT.any()))] _return_type = BYTES32_T + def prefold(self, node): + return self.evaluate(node) + def evaluate(self, node): validate_call_args(node, 1) if isinstance(node.args[0], vy_ast.Bytes): @@ -665,7 +680,7 @@ def evaluate(self, node): hash_ = f"0x{hashlib.sha256(value).hexdigest()}" return vy_ast.Hex.from_node(node, value=hash_) - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) # return a concrete type for `value` value_type = get_possible_types_from_node(node.args[0]).pop() @@ -720,6 +735,12 @@ def build_IR(self, expr, args, kwargs, context): class MethodID(FoldedFunction): _id = "method_id" + def prefold(self, node): + try: + return self.evaluate(node) + except (InvalidType, InvalidLiteral): + return + def evaluate(self, node): validate_call_args(node, 1, ["output_type"]) @@ -767,7 +788,7 @@ class ECRecover(BuiltinFunction): ] _return_type = AddressT() - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) v_t, r_t, s_t = [get_possible_types_from_node(arg).pop() for arg in node.args[1:]] return [BYTES32_T, v_t, r_t, s_t] @@ -865,7 +886,7 @@ def fetch_call_return(self, node): return_type = self.infer_kwarg_types(node)["output_type"].typedef return return_type - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) input_type = get_possible_types_from_node(node.args[0]).pop() return [input_type, UINT256_T] @@ -993,6 +1014,12 @@ def get_denomination(self, node): return denom + def prefold(self, node): + try: + return self.evaluate(node) + except InvalidLiteral: + return + def evaluate(self, node): validate_call_args(node, 2) denom = self.get_denomination(node) @@ -1015,7 +1042,7 @@ def fetch_call_return(self, node): self.infer_arg_types(node) return self._return_type - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) # return a concrete type instead of abstract type value_type = get_possible_types_from_node(node.args[0]).pop() @@ -1104,7 +1131,7 @@ def fetch_call_return(self, node): return return_type return TupleT([BoolT(), return_type]) - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) # return a concrete type for `data` data_type = get_possible_types_from_node(node.args[1]).pop() @@ -1281,7 +1308,7 @@ class RawRevert(BuiltinFunction): def fetch_call_return(self, node): return None - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) data_type = get_possible_types_from_node(node.args[0]).pop() return [data_type] @@ -1301,7 +1328,7 @@ class RawLog(BuiltinFunction): def fetch_call_return(self, node): self.infer_arg_types(node) - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) if not isinstance(node.args[0], vy_ast.List) or len(node.args[0].elements) > 4: @@ -1351,6 +1378,12 @@ class BitwiseAnd(BuiltinFunction): _return_type = UINT256_T _warned = False + def prefold(self, node): + try: + return self.evaluate(node) + except (InvalidLiteral, UnfoldableNode): + return + def evaluate(self, node): if not self.__class__._warned: vyper_warn("`bitwise_and()` is deprecated! Please use the & operator instead.") @@ -1377,6 +1410,12 @@ class BitwiseOr(BuiltinFunction): _return_type = UINT256_T _warned = False + def prefold(self, node): + try: + return self.evaluate(node) + except (UnfoldableNode, InvalidLiteral): + return + def evaluate(self, node): if not self.__class__._warned: vyper_warn("`bitwise_or()` is deprecated! Please use the | operator instead.") @@ -1403,6 +1442,12 @@ class BitwiseXor(BuiltinFunction): _return_type = UINT256_T _warned = False + def prefold(self, node): + try: + return self.evaluate(node) + except InvalidLiteral: + return + def evaluate(self, node): if not self.__class__._warned: vyper_warn("`bitwise_xor()` is deprecated! Please use the ^ operator instead.") @@ -1429,6 +1474,12 @@ class BitwiseNot(BuiltinFunction): _return_type = UINT256_T _warned = False + def prefold(self, node): + try: + return self.evaluate(node) + except InvalidLiteral: + return + def evaluate(self, node): if not self.__class__._warned: vyper_warn("`bitwise_not()` is deprecated! Please use the ~ operator instead.") @@ -1456,17 +1507,16 @@ class Shift(BuiltinFunction): _return_type = UINT256_T _warned = False - def evaluate(self, node): + def prefold(self, node): if not self.__class__._warned: vyper_warn("`shift()` is deprecated! Please use the << or >> operator instead.") self.__class__._warned = True validate_call_args(node, 2) + args = [i._metadata.get("folded_value") for i in node.args] if [i for i in node.args if not isinstance(i, vy_ast.Int)]: raise UnfoldableNode value, shift = [i.value for i in node.args] - if value < 0 or value >= 2**256: - raise InvalidLiteral("Value out of range for uint256", node.args[0]) if shift < -256 or shift > 256: # this validation is performed to prevent the compiler from hanging # rather than for correctness because the post-folded constant would @@ -1479,11 +1529,21 @@ def evaluate(self, node): value = (value << shift) % (2**256) return vy_ast.Int.from_node(node, value=value) + def evaluate(self, node): + value = args[0]._metadata.get("folded_value") + if not isinstance(value, vy_ast.Int): + raise UnfoldableNode + + if value < 0 or value >= 2**256: + raise InvalidLiteral("Value out of range for uint256", node.args[0]) + + return self.prefold(node) + def fetch_call_return(self, node): # return type is the type of the first argument return self.infer_arg_types(node)[0] - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) # return a concrete type instead of SignedIntegerAbstractType arg_ty = get_possible_types_from_node(node.args[0])[0] @@ -1508,17 +1568,30 @@ class _AddMulMod(BuiltinFunction): _inputs = [("a", UINT256_T), ("b", UINT256_T), ("c", UINT256_T)] _return_type = UINT256_T - def evaluate(self, node): + def prefold(self, node): validate_call_args(node, 3) - if isinstance(node.args[2], vy_ast.Int) and node.args[2].value == 0: - raise ZeroDivisionException("Modulo by 0", node.args[2]) + args = [i._metadata.get("folded_value") for i in node.args] + if not all(isinstance(i, vy_ast.Int) for i in args): + raise UnfoldableNode + if isinstance(args[2], vy_ast.Int) and args[2].value == 0: + raise UnfoldableNode("Modulo by 0", node.args[2]) for arg in node.args: if not isinstance(arg, vy_ast.Int): raise UnfoldableNode + + value = self._eval_fn(node.args[0].value, node.args[1].value) % node.args[2].value + return vy_ast.Int.from_node(node, value=value) + + def evaluate(self, node): + validate_call_args(node, 3) + args = [i._metadata.get("folded_value") for i in node.args] + if isinstance(args[2], vy_ast.Int) and args[2].value == 0: + raise ZeroDivisionException("Modulo by 0", node.args[2]) + for arg in node.args: if arg.value < 0 or arg.value >= 2**256: raise InvalidLiteral("Value out of range for uint256", arg) - value = self._eval_fn(node.args[0].value, node.args[1].value) % node.args[2].value + value = self._eval_fn(args[0].value, args[1].value) % args[2].value return vy_ast.Int.from_node(node, value=value) @process_inputs @@ -1959,7 +2032,7 @@ def fetch_call_return(self, node): return_type = self.infer_arg_types(node).pop() return return_type - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) types_list = get_common_types(*node.args, filter_fn=lambda x: isinstance(x, IntegerT)) @@ -2017,34 +2090,41 @@ class UnsafeDiv(_UnsafeMath): class _MinMax(BuiltinFunction): _inputs = [("a", (DecimalT(), IntegerT.any())), ("b", (DecimalT(), IntegerT.any()))] - def evaluate(self, node): + def prefold(self, node): validate_call_args(node, 2) - if not isinstance(node.args[0], type(node.args[1])): + arg0 = node.args[0]._metadata.get("folded_value") + arg1 = node.args[1]._metadata.get("folded_value") + if not isinstance(arg0, (vy_ast.Decimal, vy_ast.Int)): raise UnfoldableNode - if not isinstance(node.args[0], (vy_ast.Decimal, vy_ast.Int)): + if not isinstance(arg0, type(arg1)): raise UnfoldableNode - left, right = (i.value for i in node.args) - if isinstance(left, Decimal) and ( - min(left, right) < SizeLimits.MIN_AST_DECIMAL - or max(left, right) > SizeLimits.MAX_AST_DECIMAL + left = arg0.value + right = arg1.value + + value = self._eval_fn(left, right) + return type(node.args[0]).from_node(node, value=value) + + def evaluate(self, node): + new_node = self.prefold(node) + + left = node.args[0]._metadata.get("folded_value") + right = node.args[1]._metadata.get("folded_value") + if isinstance(left.value, Decimal) and ( + min(left.value, right.value) < SizeLimits.MIN_AST_DECIMAL + or max(left.value, right.value) > SizeLimits.MAX_AST_DECIMAL ): raise InvalidType("Decimal value is outside of allowable range", node) types_list = get_common_types( - *node.args, filter_fn=lambda x: isinstance(x, (IntegerT, DecimalT)) + *(left, right), filter_fn=lambda x: isinstance(x, (IntegerT, DecimalT)) ) if not types_list: raise TypeMismatch("Cannot perform action between dislike numeric types", node) - value = self._eval_fn(left, right) - return type(node.args[0]).from_node(node, value=value) + return new_node def fetch_call_return(self, node): - return_type = self.infer_arg_types(node).pop() - return return_type - - def infer_arg_types(self, node): self._validate_arg_types(node) types_list = get_common_types( @@ -2053,8 +2133,20 @@ def infer_arg_types(self, node): if not types_list: raise TypeMismatch("Cannot perform action between dislike numeric types", node) - type_ = types_list.pop() - return [type_, type_] + return types_list + + def infer_arg_types(self, node, expected_return_typ=None): + types_list = self.fetch_call_return(node) + + if expected_return_typ is not None: + if expected_return_typ not in types_list: + raise TypeMismatch("Cannot perform action between dislike numeric types", node) + + arg_typ = expected_return_typ + else: + arg_typ = types_list.pop() + + return [arg_typ, arg_typ] @process_inputs def build_IR(self, expr, args, kwargs, context): @@ -2098,6 +2190,9 @@ def fetch_call_return(self, node): len_needed = math.ceil(bits * math.log(2) / math.log(10)) return StringT(len_needed) + def prefold(self, node): + return self.evaluate(node) + def evaluate(self, node): validate_call_args(node, 1) if not isinstance(node.args[0], vy_ast.Int): @@ -2106,7 +2201,7 @@ def evaluate(self, node): value = str(node.args[0].value) return vy_ast.Str.from_node(node, value=value) - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) input_type = get_possible_types_from_node(node.args[0]).pop() return [input_type] @@ -2506,7 +2601,7 @@ def fetch_call_return(self, node): _, output_type = self.infer_arg_types(node) return output_type.typedef - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) validate_call_args(node, 2, ["unwrap_tuple"]) @@ -2585,6 +2680,12 @@ def build_IR(self, expr, args, kwargs, context): class _MinMaxValue(TypenameFoldedFunction): + def prefold(self, node): + try: + return self.evaluate(node) + except InvalidType: + return + def evaluate(self, node): self._validate_arg_types(node) input_type = type_from_annotation(node.args[0]) @@ -2621,6 +2722,12 @@ def _eval(self, type_): class Epsilon(TypenameFoldedFunction): _id = "epsilon" + def prefold(self, node): + try: + return self.evaluate(node) + except InvalidType: + return + def evaluate(self, node): self._validate_arg_types(node) input_type = type_from_annotation(node.args[0]) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index ce1ab983cf..e3c6c3dc28 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -607,7 +607,7 @@ def visit(self, node, typ): super().visit(node, typ) folded_value = node._metadata.get("folded_value") - if folded_value: + if isinstance(folded_value, vy_ast.Constant): # print("folded value: ", folded_value) validate_expected_type(folded_value, typ) @@ -689,7 +689,7 @@ def visit_Call(self, node: vy_ast.Call, typ: VyperType) -> None: return # builtin functions - arg_types = call_type.infer_arg_types(node) + arg_types = call_type.infer_arg_types(node, typ) # `infer_arg_types` already calls `validate_expected_type` for arg, arg_type in zip(node.args, arg_types): self.visit(arg, arg_type) diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index 425bf9d57e..3ef89b227e 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -79,6 +79,6 @@ def prefold(node: vy_ast.VyperNode, constants: dict) -> None: call_type = DISPATCH_TABLE.get(func_name) if call_type and hasattr(call_type, "evaluate"): try: - node._metadata["folded_value"] = call_type.evaluate(node) # type: ignore + node._metadata["folded_value"] = call_type.prefold(node) # type: ignore except UnfoldableNode: pass diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index baa3aa67c2..4663676779 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -281,6 +281,8 @@ def types_from_Call(self, node): var = self.get_exact_type_from_node(node.func, include_type_exprs=True) return_value = var.fetch_call_return(node) if return_value: + if isinstance(return_value, list): + return return_value return [return_value] raise InvalidType(f"{var} did not return a value", node) From 6be94e33028bbdb2e4ecfc2c4106df1214839b92 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 11:25:31 +0800 Subject: [PATCH 032/120] fix lint --- tests/functional/syntax/test_unary.py | 5 ++--- vyper/builtins/functions.py | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/functional/syntax/test_unary.py b/tests/functional/syntax/test_unary.py index 2b69f90023..c19e5ba5f0 100644 --- a/tests/functional/syntax/test_unary.py +++ b/tests/functional/syntax/test_unary.py @@ -1,8 +1,7 @@ import pytest from vyper.compiler import compile_code -from vyper.exceptions import InvalidType, TypeMismatch - +from vyper.exceptions import InvalidType fail_list = [ ( @@ -12,7 +11,7 @@ def foo() -> int128: return -2**127 """, InvalidType, - ), + ) ] diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 2a7d997d59..0e8dc46446 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -1514,9 +1514,9 @@ def prefold(self, node): validate_call_args(node, 2) args = [i._metadata.get("folded_value") for i in node.args] - if [i for i in node.args if not isinstance(i, vy_ast.Int)]: + if [i for i in args if not isinstance(i, vy_ast.Int)]: raise UnfoldableNode - value, shift = [i.value for i in node.args] + value, shift = [i.value for i in args] if shift < -256 or shift > 256: # this validation is performed to prevent the compiler from hanging # rather than for correctness because the post-folded constant would @@ -1530,7 +1530,7 @@ def prefold(self, node): return vy_ast.Int.from_node(node, value=value) def evaluate(self, node): - value = args[0]._metadata.get("folded_value") + value = node.args[0]._metadata.get("folded_value") if not isinstance(value, vy_ast.Int): raise UnfoldableNode @@ -2107,7 +2107,7 @@ def prefold(self, node): def evaluate(self, node): new_node = self.prefold(node) - + left = node.args[0]._metadata.get("folded_value") right = node.args[1]._metadata.get("folded_value") if isinstance(left.value, Decimal) and ( From 0b0c947fefaa26e0697dd3dcb18a66fc4aa9a9ae Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 16:45:58 +0800 Subject: [PATCH 033/120] fix infer_arg_types sig --- vyper/builtins/_signatures.py | 2 +- vyper/semantics/types/base.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py index 9fd59db060..f4f808f84e 100644 --- a/vyper/builtins/_signatures.py +++ b/vyper/builtins/_signatures.py @@ -127,7 +127,7 @@ def fetch_call_return(self, node): if self._return_type: return self._return_type - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) ret = [expected for (_, expected) in self._inputs] diff --git a/vyper/semantics/types/base.py b/vyper/semantics/types/base.py index c5af5c2a39..67d5453d15 100644 --- a/vyper/semantics/types/base.py +++ b/vyper/semantics/types/base.py @@ -330,7 +330,7 @@ def fetch_call_return(self, node): return self.typedef._ctor_call_return(node) raise StructureException("Value is not callable", node) - def infer_arg_types(self, node): + def infer_arg_types(self, node, expected_return_typ=None): if hasattr(self.typedef, "_ctor_arg_types"): return self.typedef._ctor_arg_types(node) raise StructureException("Value is not callable", node) From 5fa44d7097100eb9da9507c756883dcbd2192daf Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 16:46:21 +0800 Subject: [PATCH 034/120] fix prefold annotation --- vyper/ast/nodes.py | 81 ++++++++++++----------- vyper/semantics/analysis/local.py | 2 +- vyper/semantics/analysis/pre_typecheck.py | 14 ++-- 3 files changed, 49 insertions(+), 48 deletions(-) diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index b49647d9f8..342fa11d1e 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -356,8 +356,15 @@ def description(self): """ return getattr(self, "_description", type(self).__name__) - def prefold(self) -> "VyperNode": - return + def prefold(self) -> Optional["VyperNode"]: + """ + Attempt to evaluate the content of a node and generate a new node from it, + allowing for values that may be out of bounds during semantics typechecking. + + If a node cannot be prefolded, it should return None. This base method acts + as a catch-call for all inherited classes that do not implement the method. + """ + return None def evaluate(self) -> "VyperNode": """ @@ -877,12 +884,12 @@ class List(ExprNode): __slots__ = ("elements",) _translated_fields = {"elts": "elements"} - def prefold(self): - for e in self.elements: - e.prefold() + def prefold(self) -> Optional[ExprNode]: elements = [e._metadata.get("folded_value") for e in self.elements] if None not in elements: - self._metadata["folded_value"] = type(self).from_node(self, elements=elements) + return type(self).from_node(self, elements=elements) + + return None class Tuple(ExprNode): @@ -913,12 +920,13 @@ class Name(ExprNode): class UnaryOp(ExprNode): __slots__ = ("op", "operand") - def prefold(self) -> ExprNode: - self.operand.prefold() + def prefold(self) -> Optional[ExprNode]: operand = self.operand._metadata.get("folded_value") if operand is not None: value = self.op._op(operand.value) - self._metadata["folded_value"] = type(operand).from_node(self, value=value) + return type(operand).from_node(self, value=value) + + return None def evaluate(self) -> ExprNode: """ @@ -967,21 +975,21 @@ def _op(self, value): class BinOp(ExprNode): __slots__ = ("left", "op", "right") - def prefold(self) -> ExprNode: - self.left.prefold() - self.right.prefold() + def prefold(self) -> Optional[ExprNode]: left = self.left._metadata.get("folded_value") right = self.right._metadata.get("folded_value") + if None in (left, right): + return None + # this validation is performed to prevent the compiler from hanging # on very large shifts and improve the error message for negative # values. if isinstance(self.op, (LShift, RShift)) and not (0 <= right.value <= 256): - raise InvalidLiteral("Shift bits must be between 0 and 256", right) + raise InvalidLiteral("Shift bits must be between 0 and 256", self.right) - if isinstance(left, type(right)) and isinstance(left, (Int, Decimal)): - value = self.op._op(left.value, right.value) - self._metadata["folded_value"] = type(left).from_node(self, value=value) + value = self.op._op(left.value, right.value) + return type(left).from_node(self, value=value) def evaluate(self) -> ExprNode: """ @@ -998,12 +1006,6 @@ def evaluate(self) -> ExprNode: if not isinstance(left, (Int, Decimal)): raise UnfoldableNode("Node contains invalid field(s) for evaluation") - # this validation is performed to prevent the compiler from hanging - # on very large shifts and improve the error message for negative - # values. - if isinstance(self.op, (LShift, RShift)) and not (0 <= right.value <= 256): - raise InvalidLiteral("Shift bits must be between 0 and 256", right) - value = self.op._op(left.value, right.value) return type(left).from_node(self, value=value) @@ -1132,13 +1134,13 @@ class RShift(Operator): class BoolOp(ExprNode): __slots__ = ("op", "values") - def prefold(self) -> ExprNode: - for i in self.values: - i.prefold() + def prefold(self) -> Optional[ExprNode]: values = [i._metadata.get("folded_value") for i in self.values] - if None not in values: - value = self.op._op(values) - self._metadata["folded_value"] = NameConstant.from_node(self, value=value) + if None in values: + return None + + value = self.op._op(values) + return NameConstant.from_node(self, value=value) def evaluate(self) -> ExprNode: """ @@ -1196,15 +1198,15 @@ def __init__(self, *args, **kwargs): kwargs["right"] = kwargs.pop("comparators")[0] super().__init__(*args, **kwargs) - def prefold(self) -> ExprNode: - self.left.prefold() - self.right.prefold() + def prefold(self) -> Optional[ExprNode]: left = self.left._metadata.get("folded_value") right = self.right._metadata.get("folded_value") - if None not in (left, right): - value = self.op._op(left.value, right.value) - self._metadata["folded_value"] = NameConstant.from_node(self, value=value) + if None in (left, right): + return None + + value = self.op._op(left.value, right.value) + return NameConstant.from_node(self, value=value) def evaluate(self) -> ExprNode: """ @@ -1308,15 +1310,14 @@ class Attribute(ExprNode): class Subscript(ExprNode): __slots__ = ("slice", "value") - def prefold(self): - self.slice.value.prefold() - self.value.prefold() - + def prefold(self) -> Optional[ExprNode]: slice_ = self.slice.value._metadata.get("folded_value") value = self.value._metadata.get("folded_value") - if None not in (slice_, value): - self._metadata["folded_value"] = value.elements[slice_.value] + if None in (slice_, value): + return None + + return value.elements[slice_.value] def evaluate(self) -> ExprNode: """ diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index e3c6c3dc28..60c7e52141 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -415,7 +415,7 @@ def visit_For(self, node): raise InvalidType("Value must be a literal integer", args[1]) validate_expected_type(args[1], IntegerT.any()) if arg0_val.value >= arg1_val.value: - raise StructureException("Second value must be > first value", args[1]) + raise StructureExcargeption("Second value must be > first value", args[1]) if not type_list: raise TypeMismatch("Iterator values are of different types", node.iter) diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index 3ef89b227e..d4836c051a 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -48,22 +48,22 @@ def pre_typecheck(node: vy_ast.Module): def prefold(node: vy_ast.VyperNode, constants: dict) -> None: if isinstance(node, vy_ast.BinOp): - node.prefold() + node._metadata["folded_value"] = node.prefold() if isinstance(node, vy_ast.UnaryOp): - node.prefold() + node._metadata["folded_value"] = node.prefold() if isinstance(node, vy_ast.Compare): - node.prefold() + node._metadata["folded_value"] = node.prefold() if isinstance(node, vy_ast.BoolOp): - node.prefold() + node._metadata["folded_value"] = node.prefold() if isinstance(node, vy_ast.Subscript): - node.prefold() + node._metadata["folded_value"] = node.prefold() if isinstance(node, vy_ast.List): - node.prefold() + node._metadata["folded_value"] = node.prefold() if isinstance(node, vy_ast.Name): var_name = node.id @@ -77,7 +77,7 @@ def prefold(node: vy_ast.VyperNode, constants: dict) -> None: func_name = node.func.id call_type = DISPATCH_TABLE.get(func_name) - if call_type and hasattr(call_type, "evaluate"): + if call_type and hasattr(call_type, "prefold"): try: node._metadata["folded_value"] = call_type.prefold(node) # type: ignore except UnfoldableNode: From 0252268c0f31a552b07e82060692f1275c56b890 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 17:18:26 +0800 Subject: [PATCH 035/120] make prefold a wrapper over evaluate --- vyper/builtins/functions.py | 144 +++++++++++----------- vyper/semantics/analysis/pre_typecheck.py | 7 +- 2 files changed, 77 insertions(+), 74 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 0e8dc46446..7e27133be3 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -49,6 +49,7 @@ StructureException, TypeMismatch, UnfoldableNode, + VyperException, ZeroDivisionException, ) from vyper.semantics.analysis.base import VarInfo @@ -141,7 +142,10 @@ class Floor(BuiltinFunction): _return_type = INT256_T def prefold(self, node): - return self.evaluate(node) + try: + return self.evaluate(node) + except (UnfoldableNode, VyperException): + return None def evaluate(self, node): validate_call_args(node, 1) @@ -175,7 +179,10 @@ class Ceil(BuiltinFunction): _return_type = INT256_T def prefold(self, node): - return self.evaluate(node) + try: + return self.evaluate(node) + except (UnfoldableNode, VyperException): + return None def evaluate(self, node): validate_call_args(node, 1) @@ -473,7 +480,10 @@ class Len(BuiltinFunction): _return_type = UINT256_T def prefold(self, node): - return self.evaluate(node) + try: + return self.evaluate(node) + except (UnfoldableNode, VyperException): + return None def evaluate(self, node): validate_call_args(node, 1) @@ -613,17 +623,22 @@ class Keccak256(BuiltinFunction): _return_type = BYTES32_T def prefold(self, node): - return self.evaluate(node) + try: + return self.evaluate(node) + except (UnfoldableNode, VyperException): + return None def evaluate(self, node): validate_call_args(node, 1) - if isinstance(node.args[0], vy_ast.Bytes): - value = node.args[0].value - elif isinstance(node.args[0], vy_ast.Str): - value = node.args[0].value.encode() - elif isinstance(node.args[0], vy_ast.Hex): - length = len(node.args[0].value) // 2 - 1 - value = int(node.args[0].value, 16).to_bytes(length, "big") + value = node.args[0]._metadata.get("folded_value") + + if isinstance(value, vy_ast.Bytes): + value = value.value + elif isinstance(value, vy_ast.Str): + value = value.value.encode() + elif isinstance(value, vy_ast.Hex): + length = len(value.value) // 2 - 1 + value = int(value.value, 16).to_bytes(length, "big") else: raise UnfoldableNode @@ -663,7 +678,10 @@ class Sha256(BuiltinFunction): _return_type = BYTES32_T def prefold(self, node): - return self.evaluate(node) + try: + return self.evaluate(node) + except (UnfoldableNode, VyperException): + return None def evaluate(self, node): validate_call_args(node, 1) @@ -738,8 +756,8 @@ class MethodID(FoldedFunction): def prefold(self, node): try: return self.evaluate(node) - except (InvalidType, InvalidLiteral): - return + except (UnfoldableNode, VyperException): + return None def evaluate(self, node): validate_call_args(node, 1, ["output_type"]) @@ -1017,8 +1035,8 @@ def get_denomination(self, node): def prefold(self, node): try: return self.evaluate(node) - except InvalidLiteral: - return + except (UnfoldableNode, VyperException): + return None def evaluate(self, node): validate_call_args(node, 2) @@ -1381,8 +1399,8 @@ class BitwiseAnd(BuiltinFunction): def prefold(self, node): try: return self.evaluate(node) - except (InvalidLiteral, UnfoldableNode): - return + except (UnfoldableNode, VyperException): + return None def evaluate(self, node): if not self.__class__._warned: @@ -1413,8 +1431,8 @@ class BitwiseOr(BuiltinFunction): def prefold(self, node): try: return self.evaluate(node) - except (UnfoldableNode, InvalidLiteral): - return + except (UnfoldableNode, VyperException): + return None def evaluate(self, node): if not self.__class__._warned: @@ -1445,8 +1463,8 @@ class BitwiseXor(BuiltinFunction): def prefold(self, node): try: return self.evaluate(node) - except InvalidLiteral: - return + except (UnfoldableNode, VyperException): + return None def evaluate(self, node): if not self.__class__._warned: @@ -1477,8 +1495,8 @@ class BitwiseNot(BuiltinFunction): def prefold(self, node): try: return self.evaluate(node) - except InvalidLiteral: - return + except (UnfoldableNode, VyperException): + return None def evaluate(self, node): if not self.__class__._warned: @@ -1508,6 +1526,12 @@ class Shift(BuiltinFunction): _warned = False def prefold(self, node): + try: + return self.evaluate(node) + except (UnfoldableNode, VyperException): + return None + + def evaluate(self, node): if not self.__class__._warned: vyper_warn("`shift()` is deprecated! Please use the << or >> operator instead.") self.__class__._warned = True @@ -1517,6 +1541,8 @@ def prefold(self, node): if [i for i in args if not isinstance(i, vy_ast.Int)]: raise UnfoldableNode value, shift = [i.value for i in args] + if value < 0 or value >= 2**256: + raise InvalidLiteral("Value out of range for uint256", node.args[0]) if shift < -256 or shift > 256: # this validation is performed to prevent the compiler from hanging # rather than for correctness because the post-folded constant would @@ -1529,16 +1555,6 @@ def prefold(self, node): value = (value << shift) % (2**256) return vy_ast.Int.from_node(node, value=value) - def evaluate(self, node): - value = node.args[0]._metadata.get("folded_value") - if not isinstance(value, vy_ast.Int): - raise UnfoldableNode - - if value < 0 or value >= 2**256: - raise InvalidLiteral("Value out of range for uint256", node.args[0]) - - return self.prefold(node) - def fetch_call_return(self, node): # return type is the type of the first argument return self.infer_arg_types(node)[0] @@ -1569,26 +1585,20 @@ class _AddMulMod(BuiltinFunction): _return_type = UINT256_T def prefold(self, node): - validate_call_args(node, 3) - args = [i._metadata.get("folded_value") for i in node.args] - if not all(isinstance(i, vy_ast.Int) for i in args): - raise UnfoldableNode - if isinstance(args[2], vy_ast.Int) and args[2].value == 0: - raise UnfoldableNode("Modulo by 0", node.args[2]) - for arg in node.args: - if not isinstance(arg, vy_ast.Int): - raise UnfoldableNode - - value = self._eval_fn(node.args[0].value, node.args[1].value) % node.args[2].value - return vy_ast.Int.from_node(node, value=value) + try: + return self.evaluate(node) + except (UnfoldableNode, VyperException): + return None def evaluate(self, node): validate_call_args(node, 3) args = [i._metadata.get("folded_value") for i in node.args] if isinstance(args[2], vy_ast.Int) and args[2].value == 0: raise ZeroDivisionException("Modulo by 0", node.args[2]) - for arg in node.args: - if arg.value < 0 or arg.value >= 2**256: + for arg, prefolded in zip(node.args, args): + if not isinstance(prefolded, vy_ast.Int): + raise UnfoldableNode + if prefolded.value < 0 or prefolded.value >= 2**256: raise InvalidLiteral("Value out of range for uint256", arg) value = self._eval_fn(args[0].value, args[1].value) % args[2].value @@ -2091,25 +2101,17 @@ class _MinMax(BuiltinFunction): _inputs = [("a", (DecimalT(), IntegerT.any())), ("b", (DecimalT(), IntegerT.any()))] def prefold(self, node): - validate_call_args(node, 2) - arg0 = node.args[0]._metadata.get("folded_value") - arg1 = node.args[1]._metadata.get("folded_value") - if not isinstance(arg0, (vy_ast.Decimal, vy_ast.Int)): - raise UnfoldableNode - if not isinstance(arg0, type(arg1)): - raise UnfoldableNode - - left = arg0.value - right = arg1.value - - value = self._eval_fn(left, right) - return type(node.args[0]).from_node(node, value=value) + try: + return self.evaluate(node) + except (UnfoldableNode, VyperException): + return None def evaluate(self, node): - new_node = self.prefold(node) - left = node.args[0]._metadata.get("folded_value") right = node.args[1]._metadata.get("folded_value") + if None in (left, right): + raise UnfoldableNode + if isinstance(left.value, Decimal) and ( min(left.value, right.value) < SizeLimits.MIN_AST_DECIMAL or max(left.value, right.value) > SizeLimits.MAX_AST_DECIMAL @@ -2122,7 +2124,8 @@ def evaluate(self, node): if not types_list: raise TypeMismatch("Cannot perform action between dislike numeric types", node) - return new_node + value = self._eval_fn(left.value, right.value) + return type(node.args[0]).from_node(node, value=value) def fetch_call_return(self, node): self._validate_arg_types(node) @@ -2191,7 +2194,10 @@ def fetch_call_return(self, node): return StringT(len_needed) def prefold(self, node): - return self.evaluate(node) + try: + return self.evaluate(node) + except (UnfoldableNode, VyperException): + return None def evaluate(self, node): validate_call_args(node, 1) @@ -2683,8 +2689,8 @@ class _MinMaxValue(TypenameFoldedFunction): def prefold(self, node): try: return self.evaluate(node) - except InvalidType: - return + except (UnfoldableNode, VyperException): + return None def evaluate(self, node): self._validate_arg_types(node) @@ -2725,8 +2731,8 @@ class Epsilon(TypenameFoldedFunction): def prefold(self, node): try: return self.evaluate(node) - except InvalidType: - return + except (UnfoldableNode, VyperException): + return None def evaluate(self, node): self._validate_arg_types(node) diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index d4836c051a..0e55b7c5a6 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -1,5 +1,5 @@ from vyper import ast as vy_ast -from vyper.exceptions import UnfoldableNode +from vyper.exceptions import VyperException def get_constants(node: vy_ast.Module) -> dict: @@ -78,7 +78,4 @@ def prefold(node: vy_ast.VyperNode, constants: dict) -> None: call_type = DISPATCH_TABLE.get(func_name) if call_type and hasattr(call_type, "prefold"): - try: - node._metadata["folded_value"] = call_type.prefold(node) # type: ignore - except UnfoldableNode: - pass + node._metadata["folded_value"] = call_type.prefold(node) # type: ignore From e46f5289720d6e18a033a7299e82032d35f23c42 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 17:18:34 +0800 Subject: [PATCH 036/120] fix abs test --- tests/functional/builtins/folding/test_abs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/builtins/folding/test_abs.py b/tests/functional/builtins/folding/test_abs.py index 629d5baf52..f450e434a4 100644 --- a/tests/functional/builtins/folding/test_abs.py +++ b/tests/functional/builtins/folding/test_abs.py @@ -4,7 +4,7 @@ from vyper import ast as vy_ast from vyper.builtins import functions as vy_fn -from vyper.exceptions import OverflowException +from vyper.exceptions import InvalidType, OverflowException @pytest.mark.fuzzing @@ -35,7 +35,7 @@ def test_abs_upper_bound_folding(get_contract, a): def foo(a: int256) -> int256: return abs({a}) """ - with pytest.raises(OverflowException): + with pytest.raises(InvalidType): get_contract(source) From f96d25fd6c8b9d4daa1e45f5f9556d4029296864 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 18:01:44 +0800 Subject: [PATCH 037/120] fix lint --- vyper/semantics/analysis/pre_typecheck.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index 0e55b7c5a6..f6bb6389a0 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -1,5 +1,4 @@ from vyper import ast as vy_ast -from vyper.exceptions import VyperException def get_constants(node: vy_ast.Module) -> dict: From d961beb1746fe757f268e48f2df228f5b287a3f7 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 18:07:32 +0800 Subject: [PATCH 038/120] fix typo --- vyper/semantics/analysis/local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 60c7e52141..e3c6c3dc28 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -415,7 +415,7 @@ def visit_For(self, node): raise InvalidType("Value must be a literal integer", args[1]) validate_expected_type(args[1], IntegerT.any()) if arg0_val.value >= arg1_val.value: - raise StructureExcargeption("Second value must be > first value", args[1]) + raise StructureException("Second value must be > first value", args[1]) if not type_list: raise TypeMismatch("Iterator values are of different types", node.iter) From 3c37c52255ebfd536e4f9f8a63d47f8535b34d4f Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 18:12:31 +0800 Subject: [PATCH 039/120] fix interface tests --- tests/functional/builtins/codegen/test_interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/builtins/codegen/test_interfaces.py b/tests/functional/builtins/codegen/test_interfaces.py index 8cb0124f29..4570f27345 100644 --- a/tests/functional/builtins/codegen/test_interfaces.py +++ b/tests/functional/builtins/codegen/test_interfaces.py @@ -408,7 +408,7 @@ def ok() -> {typ}: @external def should_fail() -> int256: - return -2**255 # OOB for all int/uint types with less than 256 bits + return min_value(int256) """ code = f""" From 23fe4021b5d2f68b19aef5b91792442b68de5ab3 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 18:19:09 +0800 Subject: [PATCH 040/120] fix some builtins wip --- vyper/builtins/functions.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 7e27133be3..cf2c47711f 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -1019,15 +1019,16 @@ class AsWeiValue(BuiltinFunction): } def get_denomination(self, node): - if not isinstance(node.args[1], vy_ast.Str): + value = node.args[1]._metadata.get("folded_value") + if not isinstance(value, vy_ast.Str): raise ArgumentException( "Wei denomination must be given as a literal string", node.args[1] ) try: - denom = next(v for k, v in self.wei_denoms.items() if node.args[1].value in k) + denom = next(v for k, v in self.wei_denoms.items() if value.value in k) except StopIteration: raise ArgumentException( - f"Unknown denomination: {node.args[1].value}", node.args[1] + f"Unknown denomination: {value.value}", node.args[1] ) from None return denom @@ -1042,9 +1043,10 @@ def evaluate(self, node): validate_call_args(node, 2) denom = self.get_denomination(node) - if not isinstance(node.args[0], (vy_ast.Decimal, vy_ast.Int)): + value = node.args[0]._metadata.get("folded_value") + if not isinstance(value, (vy_ast.Decimal, vy_ast.Int)): raise UnfoldableNode - value = node.args[0].value + value = value.value if value < 0: raise InvalidLiteral("Negative wei value not allowed", node.args[0]) @@ -2107,9 +2109,13 @@ def prefold(self, node): return None def evaluate(self, node): + validate_call_args(node, 2) + left = node.args[0]._metadata.get("folded_value") right = node.args[1]._metadata.get("folded_value") - if None in (left, right): + if not isinstance(left, type(right)): + raise UnfoldableNode + if not isinstance(left, (vy_ast.Decimal, vy_ast.Int)): raise UnfoldableNode if isinstance(left.value, Decimal) and ( From dd8cb8fe1cf7a7c75f065b606a390731c13b8021 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 18:19:15 +0800 Subject: [PATCH 041/120] fix tests --- .../functional/syntax/exceptions/test_argument_exception.py | 2 +- tests/functional/syntax/test_as_wei_value.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/functional/syntax/exceptions/test_argument_exception.py b/tests/functional/syntax/exceptions/test_argument_exception.py index fc06395015..939cc7104b 100644 --- a/tests/functional/syntax/exceptions/test_argument_exception.py +++ b/tests/functional/syntax/exceptions/test_argument_exception.py @@ -7,7 +7,7 @@ """ @external def foo(): - x = as_wei_value(5, "vader") + x: uint256 = as_wei_value(5, "vader") """, """ @external diff --git a/tests/functional/syntax/test_as_wei_value.py b/tests/functional/syntax/test_as_wei_value.py index a5232a5c9a..04e4ee6b26 100644 --- a/tests/functional/syntax/test_as_wei_value.py +++ b/tests/functional/syntax/test_as_wei_value.py @@ -7,7 +7,7 @@ """ @external def foo(): - x: int128 = as_wei_value(5, szabo) + x: uint256 = as_wei_value(5, "szaboo") """, ArgumentException, ), @@ -59,6 +59,10 @@ def foo() -> uint256: x: address = 0x1234567890123456789012345678901234567890 return x.balance """, + """ +y: constant(String[5]) = "szabo" +x: constant(uint256) = as_wei_value(5, y) + """, ] From 93c2e319bbd9ba846b07b3d1c74da7deab41fdd5 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 21:19:48 +0800 Subject: [PATCH 042/120] fix interface repr --- vyper/semantics/analysis/local.py | 1 - vyper/semantics/types/function.py | 2 +- vyper/semantics/types/user.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index e3c6c3dc28..e6c931e4fb 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -608,7 +608,6 @@ def visit(self, node, typ): folded_value = node._metadata.get("folded_value") if isinstance(folded_value, vy_ast.Constant): - # print("folded value: ", folded_value) validate_expected_type(folded_value, typ) # annotate diff --git a/vyper/semantics/types/function.py b/vyper/semantics/types/function.py index ee43939320..6aefaf575c 100644 --- a/vyper/semantics/types/function.py +++ b/vyper/semantics/types/function.py @@ -117,7 +117,7 @@ def __repr__(self): def __str__(self): ret_sig = "" if not self.return_type else f" -> {self.return_type}" args_sig = ",".join([str(t) for t in self.argument_types]) - return f"def {self.name} {args_sig}{ret_sig}:" + return f"def {self.name}({args_sig}){ret_sig}:" # override parent implementation. function type equality does not # make too much sense. diff --git a/vyper/semantics/types/user.py b/vyper/semantics/types/user.py index ce82731c34..f81132ea52 100644 --- a/vyper/semantics/types/user.py +++ b/vyper/semantics/types/user.py @@ -295,7 +295,7 @@ def abi_type(self) -> ABIType: return ABI_Address() def __repr__(self): - return f"{self._id}" + return f"interface ({self.functions})" # when using the type itself (not an instance) in the call position # maybe rename to _ctor_call_return From 02af1656538f1fcb6211460df035e6ca9501feae Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 21:19:58 +0800 Subject: [PATCH 043/120] add annotated ast output --- vyper/compiler/output.py | 2 +- vyper/compiler/phases.py | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/vyper/compiler/output.py b/vyper/compiler/output.py index e47f300ba9..643d8ebbd2 100644 --- a/vyper/compiler/output.py +++ b/vyper/compiler/output.py @@ -17,7 +17,7 @@ def build_ast_dict(compiler_data: CompilerData) -> dict: ast_dict = { "contract_name": str(compiler_data.contract_path), - "ast": ast_to_dict(compiler_data.vyper_module), + "ast": ast_to_dict(compiler_data.vyper_module_annotated), } return ast_dict diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index ff586e300b..44d7e7ad82 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -32,8 +32,10 @@ class CompilerData: ---------- vyper_module : vy_ast.Module Top-level Vyper AST node + vyper_module_annotated : vy_ast.Module + Annotated but unfolded Vyper AST vyper_module_folded : vy_ast.Module - Folded Vyper AST + Annotated and folded Vyper AST global_ctx : GlobalContext Sorted, contextualized representation of the Vyper AST ir_nodes : IRnode @@ -131,16 +133,13 @@ def vyper_module(self): return self._generate_ast @cached_property - def vyper_module_unfolded(self) -> vy_ast.Module: - # This phase is intended to generate an AST for tooling use, and is not - # used in the compilation process. - - return generate_unfolded_ast(self.contract_path, self.vyper_module, self.input_bundle) + def vyper_module_annotated(self) -> vy_ast.Module: + return generate_annotated_ast(self.contract_path, self.vyper_module, self.input_bundle) @cached_property def _folded_module(self): return generate_folded_ast( - self.contract_path, self.vyper_module, self.input_bundle, self.storage_layout_override + self.contract_path, self.vyper_module_annotated, self.input_bundle, self.storage_layout_override ) @property @@ -234,11 +233,10 @@ def generate_ast( # destructive -- mutates module in place! -def generate_unfolded_ast( +def generate_annotated_ast( contract_path: Path | PurePath, vyper_module: vy_ast.Module, input_bundle: InputBundle ) -> vy_ast.Module: vy_ast.validation.validate_literal_nodes(vyper_module) - vy_ast.folding.replace_builtin_functions(vyper_module) with input_bundle.search_path(contract_path.parent): # note: validate_semantics does type inference on the AST From 3b59708ace9cfde2007a1ae80f8abbed355196b6 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 21:28:45 +0800 Subject: [PATCH 044/120] fix compiler phases --- vyper/compiler/phases.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 44d7e7ad82..4bc78e15bc 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -262,16 +262,10 @@ def generate_folded_ast( Returns ------- vy_ast.Module - Folded Vyper AST + Annotated Vyper AST StorageLayout Layout of variables in storage """ - - vy_ast.validation.validate_literal_nodes(vyper_module) - - with input_bundle.search_path(contract_path.parent): - validate_semantics(vyper_module, input_bundle) - symbol_tables = set_data_positions(vyper_module, storage_layout_overrides) vyper_module_folded = copy.deepcopy(vyper_module) From 0bfaf2f4e26288798d7825f390ebbd495e704b4f Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 21:30:01 +0800 Subject: [PATCH 045/120] add prefold to builtin fn sig --- vyper/builtins/_signatures.py | 11 ++- vyper/builtins/functions.py | 102 ---------------------- vyper/semantics/analysis/pre_typecheck.py | 2 +- 3 files changed, 11 insertions(+), 104 deletions(-) diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py index f4f808f84e..270176ce08 100644 --- a/vyper/builtins/_signatures.py +++ b/vyper/builtins/_signatures.py @@ -4,7 +4,7 @@ from vyper.ast.validation import validate_call_args from vyper.codegen.expr import Expr from vyper.codegen.ir_node import IRnode -from vyper.exceptions import CompilerPanic, TypeMismatch +from vyper.exceptions import CompilerPanic, TypeMismatch, UnfoldableNode, VyperException from vyper.semantics.analysis.utils import ( check_kwargable, get_exact_type_from_node, @@ -121,6 +121,15 @@ def _validate_arg_types(self, node): # ensures the type can be inferred exactly. get_exact_type_from_node(arg) + def prefold(self, node): + if not hasattr(self, "evaluate"): + return None + + try: + return self.evaluate(node) + except (UnfoldableNode, VyperException): + return None + def fetch_call_return(self, node): self._validate_arg_types(node) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index cf2c47711f..223830fd59 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -141,12 +141,6 @@ class Floor(BuiltinFunction): # TODO: maybe use int136? _return_type = INT256_T - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): validate_call_args(node, 1) value = node.args[0]._metadata.get("folded_value") @@ -178,12 +172,6 @@ class Ceil(BuiltinFunction): # TODO: maybe use int136? _return_type = INT256_T - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): validate_call_args(node, 1) value = node.args[0]._metadata.get("folded_value") @@ -479,12 +467,6 @@ class Len(BuiltinFunction): _inputs = [("b", (StringT.any(), BytesT.any(), DArrayT.any()))] _return_type = UINT256_T - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): validate_call_args(node, 1) arg = node.args[0] @@ -622,12 +604,6 @@ class Keccak256(BuiltinFunction): _inputs = [("value", (BytesT.any(), BYTES32_T, StringT.any()))] _return_type = BYTES32_T - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): validate_call_args(node, 1) value = node.args[0]._metadata.get("folded_value") @@ -677,12 +653,6 @@ class Sha256(BuiltinFunction): _inputs = [("value", (BYTES32_T, BytesT.any(), StringT.any()))] _return_type = BYTES32_T - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): validate_call_args(node, 1) if isinstance(node.args[0], vy_ast.Bytes): @@ -753,12 +723,6 @@ def build_IR(self, expr, args, kwargs, context): class MethodID(FoldedFunction): _id = "method_id" - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): validate_call_args(node, 1, ["output_type"]) @@ -1033,12 +997,6 @@ def get_denomination(self, node): return denom - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): validate_call_args(node, 2) denom = self.get_denomination(node) @@ -1398,12 +1356,6 @@ class BitwiseAnd(BuiltinFunction): _return_type = UINT256_T _warned = False - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): if not self.__class__._warned: vyper_warn("`bitwise_and()` is deprecated! Please use the & operator instead.") @@ -1430,12 +1382,6 @@ class BitwiseOr(BuiltinFunction): _return_type = UINT256_T _warned = False - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): if not self.__class__._warned: vyper_warn("`bitwise_or()` is deprecated! Please use the | operator instead.") @@ -1462,12 +1408,6 @@ class BitwiseXor(BuiltinFunction): _return_type = UINT256_T _warned = False - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): if not self.__class__._warned: vyper_warn("`bitwise_xor()` is deprecated! Please use the ^ operator instead.") @@ -1494,12 +1434,6 @@ class BitwiseNot(BuiltinFunction): _return_type = UINT256_T _warned = False - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): if not self.__class__._warned: vyper_warn("`bitwise_not()` is deprecated! Please use the ~ operator instead.") @@ -1527,12 +1461,6 @@ class Shift(BuiltinFunction): _return_type = UINT256_T _warned = False - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): if not self.__class__._warned: vyper_warn("`shift()` is deprecated! Please use the << or >> operator instead.") @@ -1586,12 +1514,6 @@ class _AddMulMod(BuiltinFunction): _inputs = [("a", UINT256_T), ("b", UINT256_T), ("c", UINT256_T)] _return_type = UINT256_T - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): validate_call_args(node, 3) args = [i._metadata.get("folded_value") for i in node.args] @@ -2102,12 +2024,6 @@ class UnsafeDiv(_UnsafeMath): class _MinMax(BuiltinFunction): _inputs = [("a", (DecimalT(), IntegerT.any())), ("b", (DecimalT(), IntegerT.any()))] - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): validate_call_args(node, 2) @@ -2199,12 +2115,6 @@ def fetch_call_return(self, node): len_needed = math.ceil(bits * math.log(2) / math.log(10)) return StringT(len_needed) - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): validate_call_args(node, 1) if not isinstance(node.args[0], vy_ast.Int): @@ -2692,12 +2602,6 @@ def build_IR(self, expr, args, kwargs, context): class _MinMaxValue(TypenameFoldedFunction): - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): self._validate_arg_types(node) input_type = type_from_annotation(node.args[0]) @@ -2734,12 +2638,6 @@ def _eval(self, type_): class Epsilon(TypenameFoldedFunction): _id = "epsilon" - def prefold(self, node): - try: - return self.evaluate(node) - except (UnfoldableNode, VyperException): - return None - def evaluate(self, node): self._validate_arg_types(node) input_type = type_from_annotation(node.args[0]) diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index f6bb6389a0..3c3cc96501 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -76,5 +76,5 @@ def prefold(node: vy_ast.VyperNode, constants: dict) -> None: func_name = node.func.id call_type = DISPATCH_TABLE.get(func_name) - if call_type and hasattr(call_type, "prefold"): + if call_type: node._metadata["folded_value"] = call_type.prefold(node) # type: ignore From 78bad6c168bfe2df73cf214719f4f1aba7b381f8 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 21:30:57 +0800 Subject: [PATCH 046/120] clean up prefold --- vyper/semantics/analysis/pre_typecheck.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index 3c3cc96501..67adf0e209 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -46,22 +46,7 @@ def pre_typecheck(node: vy_ast.Module): def prefold(node: vy_ast.VyperNode, constants: dict) -> None: - if isinstance(node, vy_ast.BinOp): - node._metadata["folded_value"] = node.prefold() - - if isinstance(node, vy_ast.UnaryOp): - node._metadata["folded_value"] = node.prefold() - - if isinstance(node, vy_ast.Compare): - node._metadata["folded_value"] = node.prefold() - - if isinstance(node, vy_ast.BoolOp): - node._metadata["folded_value"] = node.prefold() - - if isinstance(node, vy_ast.Subscript): - node._metadata["folded_value"] = node.prefold() - - if isinstance(node, vy_ast.List): + if isinstance(node, (vy_ast.BinOp, vy_ast.BoolOp, vy_ast.Compare, vy_ast.List, vy_ast.Subscript, vy_ast.UnaryOp)): node._metadata["folded_value"] = node.prefold() if isinstance(node, vy_ast.Name): From 524fb8f9d084daf9e082a060fb99d9098773801b Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 21:31:29 +0800 Subject: [PATCH 047/120] fix lint --- vyper/ast/nodes.py | 6 +++--- vyper/builtins/_signatures.py | 2 +- vyper/builtins/functions.py | 5 +---- vyper/compiler/phases.py | 7 +++++-- vyper/semantics/analysis/pre_typecheck.py | 12 +++++++++++- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index 342fa11d1e..578a5952c7 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -888,7 +888,7 @@ def prefold(self) -> Optional[ExprNode]: elements = [e._metadata.get("folded_value") for e in self.elements] if None not in elements: return type(self).from_node(self, elements=elements) - + return None @@ -925,7 +925,7 @@ def prefold(self) -> Optional[ExprNode]: if operand is not None: value = self.op._op(operand.value) return type(operand).from_node(self, value=value) - + return None def evaluate(self) -> ExprNode: @@ -1204,7 +1204,7 @@ def prefold(self) -> Optional[ExprNode]: if None in (left, right): return None - + value = self.op._op(left.value, right.value) return NameConstant.from_node(self, value=value) diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py index 270176ce08..44387095a3 100644 --- a/vyper/builtins/_signatures.py +++ b/vyper/builtins/_signatures.py @@ -124,7 +124,7 @@ def _validate_arg_types(self, node): def prefold(self, node): if not hasattr(self, "evaluate"): return None - + try: return self.evaluate(node) except (UnfoldableNode, VyperException): diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 223830fd59..9240df0f21 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -49,7 +49,6 @@ StructureException, TypeMismatch, UnfoldableNode, - VyperException, ZeroDivisionException, ) from vyper.semantics.analysis.base import VarInfo @@ -991,9 +990,7 @@ def get_denomination(self, node): try: denom = next(v for k, v in self.wei_denoms.items() if value.value in k) except StopIteration: - raise ArgumentException( - f"Unknown denomination: {value.value}", node.args[1] - ) from None + raise ArgumentException(f"Unknown denomination: {value.value}", node.args[1]) from None return denom diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 4bc78e15bc..0fb56eb918 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -33,7 +33,7 @@ class CompilerData: vyper_module : vy_ast.Module Top-level Vyper AST node vyper_module_annotated : vy_ast.Module - Annotated but unfolded Vyper AST + Annotated but unfolded Vyper AST vyper_module_folded : vy_ast.Module Annotated and folded Vyper AST global_ctx : GlobalContext @@ -139,7 +139,10 @@ def vyper_module_annotated(self) -> vy_ast.Module: @cached_property def _folded_module(self): return generate_folded_ast( - self.contract_path, self.vyper_module_annotated, self.input_bundle, self.storage_layout_override + self.contract_path, + self.vyper_module_annotated, + self.input_bundle, + self.storage_layout_override, ) @property diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index 67adf0e209..5eaf901d0f 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -46,7 +46,17 @@ def pre_typecheck(node: vy_ast.Module): def prefold(node: vy_ast.VyperNode, constants: dict) -> None: - if isinstance(node, (vy_ast.BinOp, vy_ast.BoolOp, vy_ast.Compare, vy_ast.List, vy_ast.Subscript, vy_ast.UnaryOp)): + if isinstance( + node, + ( + vy_ast.BinOp, + vy_ast.BoolOp, + vy_ast.Compare, + vy_ast.List, + vy_ast.Subscript, + vy_ast.UnaryOp, + ), + ): node._metadata["folded_value"] = node.prefold() if isinstance(node, vy_ast.Name): From 92a33cff9e74bc8fee3989db5d6e84f6f49067a7 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 21:49:53 +0800 Subject: [PATCH 048/120] fix mypy lint --- vyper/ast/nodes.pyi | 1 + vyper/semantics/analysis/pre_typecheck.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/vyper/ast/nodes.pyi b/vyper/ast/nodes.pyi index 47c9af8526..2d2e35ffae 100644 --- a/vyper/ast/nodes.pyi +++ b/vyper/ast/nodes.pyi @@ -26,6 +26,7 @@ class VyperNode: def description(self): ... @classmethod def get_fields(cls: Any) -> set: ... + def prefold(self) -> Optional[VyperNode]: ... def evaluate(self) -> VyperNode: ... @classmethod def from_node(cls, node: VyperNode, **kwargs: Any) -> Any: ... diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index 5eaf901d0f..76c31aaa04 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -2,7 +2,7 @@ def get_constants(node: vy_ast.Module) -> dict: - constants = {} + constants: dict[str, vy_ast.VyperNode] = {} module_nodes = node.body.copy() const_var_decls = [ n for n in module_nodes if isinstance(n, vy_ast.VariableDecl) and n.is_constant @@ -45,7 +45,7 @@ def pre_typecheck(node: vy_ast.Module): prefold(n, constants) -def prefold(node: vy_ast.VyperNode, constants: dict) -> None: +def prefold(node: vy_ast.VyperNode, constants: dict[str, vy_ast.VyperNode]) -> None: if isinstance( node, ( From 28e202f389bf1a52277d75ba0619c99563c03b57 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 21:56:36 +0800 Subject: [PATCH 049/120] fix ast dict test --- tests/unit/ast/test_ast_dict.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/ast/test_ast_dict.py b/tests/unit/ast/test_ast_dict.py index 1f60c9ac8b..feeec8908b 100644 --- a/tests/unit/ast/test_ast_dict.py +++ b/tests/unit/ast/test_ast_dict.py @@ -68,12 +68,14 @@ def test_basic_ast(): "lineno": 2, "node_id": 2, "src": "1:1:0", + "type": "int128", }, "value": None, "is_constant": False, "is_immutable": False, "is_public": False, "is_transient": False, + "type": "int128", } From 07f2026df43588a51805245edbd5bddf6fe2aaa4 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 14 Nov 2023 22:37:55 +0800 Subject: [PATCH 050/120] fix mypy --- vyper/semantics/analysis/pre_typecheck.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index 76c31aaa04..6564440c1e 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -35,7 +35,7 @@ def get_constants(node: vy_ast.Module) -> dict: return constants -def pre_typecheck(node: vy_ast.Module): +def pre_typecheck(node: vy_ast.Module) -> None: constants = get_constants(node) for n in node.get_descendants(reverse=True): From 092f71b1aadd8aaadae0feeeabf0d8f505c1ab7f Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 15 Nov 2023 10:59:36 +0800 Subject: [PATCH 051/120] add tests --- .../builtins/codegen/test_keccak256.py | 31 +++++++++++ .../builtins/codegen/test_sha256.py | 30 +++++++++++ .../test_default_parameters.py | 22 ++++++++ .../codegen/types/test_dynamic_array.py | 16 ++++++ tests/functional/syntax/test_abi_decode.py | 4 +- tests/functional/syntax/test_abs.py | 15 ++++++ tests/functional/syntax/test_addmulmod.py | 22 ++++++++ tests/functional/syntax/test_ceil.py | 18 +++++++ tests/functional/syntax/test_floor.py | 18 +++++++ tests/functional/syntax/test_for_range.py | 52 ++++++++++++++++++- tests/functional/syntax/test_len.py | 8 +++ tests/functional/syntax/test_minmax.py | 20 +++++++ tests/functional/syntax/test_powmod.py | 16 ++++++ tests/functional/syntax/test_raw_call.py | 14 +++++ tests/functional/syntax/test_uint2str.py | 15 ++++++ vyper/semantics/analysis/utils.py | 2 +- 16 files changed, 299 insertions(+), 4 deletions(-) create mode 100644 tests/functional/syntax/test_abs.py create mode 100644 tests/functional/syntax/test_ceil.py create mode 100644 tests/functional/syntax/test_floor.py create mode 100644 tests/functional/syntax/test_powmod.py create mode 100644 tests/functional/syntax/test_uint2str.py diff --git a/tests/functional/builtins/codegen/test_keccak256.py b/tests/functional/builtins/codegen/test_keccak256.py index 90fa8b9e09..e9a89a06a1 100644 --- a/tests/functional/builtins/codegen/test_keccak256.py +++ b/tests/functional/builtins/codegen/test_keccak256.py @@ -1,3 +1,6 @@ +from vyper.utils import hex_to_int + + def test_hash_code(get_contract_with_gas_estimation, keccak): hash_code = """ @external @@ -80,3 +83,31 @@ def try32(inp: bytes32) -> bool: assert c.tryy(b"\x35" * 33) is True print("Passed KECCAK256 hash test") + + +def test_hash_constant_bytes32(get_contract_with_gas_estimation, keccak): + hex_val = "0x1234567890123456789012345678901234567890123456789012345678901234" + code = f""" +FOO: constant(bytes32) = {hex_val} +BAR: constant(bytes32) = keccak256(FOO) +@external +def foo() -> bytes32: + x: bytes32 = BAR + return x + """ + c = get_contract_with_gas_estimation(code) + assert "0x" + c.foo().hex() == keccak(hex_to_int(hex_val).to_bytes(32, "big")).hex() + + +def test_hash_constant_string(get_contract_with_gas_estimation, keccak): + str_val = "0x1234567890123456789012345678901234567890123456789012345678901234" + code = f""" +FOO: constant(String[66]) = "{str_val}" +BAR: constant(bytes32) = keccak256(FOO) +@external +def foo() -> bytes32: + x: bytes32 = BAR + return x + """ + c = get_contract_with_gas_estimation(code) + assert "0x" + c.foo().hex() == keccak(str_val.encode()).hex() \ No newline at end of file diff --git a/tests/functional/builtins/codegen/test_sha256.py b/tests/functional/builtins/codegen/test_sha256.py index 468e684645..e9dd8736c0 100644 --- a/tests/functional/builtins/codegen/test_sha256.py +++ b/tests/functional/builtins/codegen/test_sha256.py @@ -2,6 +2,8 @@ import pytest +from vyper.utils import hex_to_int + pytestmark = pytest.mark.usefixtures("memory_mocker") @@ -77,3 +79,31 @@ def bar() -> bytes32: c.set(test_val, transact={}) assert c.a() == test_val assert c.bar() == hashlib.sha256(test_val).digest() + + +def test_sha256_constant_bytes32(get_contract_with_gas_estimation): + hex_val = "0x1234567890123456789012345678901234567890123456789012345678901234" + code = f""" +FOO: constant(bytes32) = {hex_val} +BAR: constant(bytes32) = sha256(FOO) +@external +def foo() -> bytes32: + x: bytes32 = BAR + return x + """ + c = get_contract_with_gas_estimation(code) + assert c.foo() == hashlib.sha256(hex_to_int(hex_val).to_bytes(32, "big")).digest() + + +def test_sha256_constant_string(get_contract_with_gas_estimation): + str_val = "0x1234567890123456789012345678901234567890123456789012345678901234" + code = f""" +FOO: constant(String[66]) = "{str_val}" +BAR: constant(bytes32) = sha256(FOO) +@external +def foo() -> bytes32: + x: bytes32 = BAR + return x + """ + c = get_contract_with_gas_estimation(code) + assert c.foo() == hashlib.sha256(str_val.encode()).digest() \ No newline at end of file diff --git a/tests/functional/codegen/calling_convention/test_default_parameters.py b/tests/functional/codegen/calling_convention/test_default_parameters.py index a90f5e6624..7a5673c281 100644 --- a/tests/functional/codegen/calling_convention/test_default_parameters.py +++ b/tests/functional/codegen/calling_convention/test_default_parameters.py @@ -304,6 +304,28 @@ def foo(a: address = empty(address)): def foo(a: int112 = min_value(int112)): self.A = a """, + """ +struct X: + x: int128 + y: address +BAR: constant(X) = X({x: 1, y: 0x0000000000000000000000000000000000012345}) +@external +def out_literals(a: int128 = BAR.x + 1) -> X: + return BAR + """, + """ +struct X: + x: int128 + y: address +struct Y: + x: X + y: uint256 +BAR: constant(X) = X({x: 1, y: 0x0000000000000000000000000000000000012345}) +FOO: constant(Y) = Y({x: BAR, y: 256}) +@external +def out_literals(a: int128 = FOO.x.x + 1) -> Y: + return FOO + """, ] diff --git a/tests/functional/codegen/types/test_dynamic_array.py b/tests/functional/codegen/types/test_dynamic_array.py index 41a8984555..f463f8d92b 100644 --- a/tests/functional/codegen/types/test_dynamic_array.py +++ b/tests/functional/codegen/types/test_dynamic_array.py @@ -315,6 +315,22 @@ def test_array(x: int128, y: int128, z: int128, w: int128) -> int128: def test_array_negative_accessor(get_contract_with_gas_estimation, assert_compile_failed): + array_constant_negative_accessor = """ +FOO: constant(int128) = -1 +@external +def test_array(x: int128, y: int128, z: int128, w: int128) -> int128: + a: int128[4] = [0, 0, 0, 0] + a[0] = x + a[1] = y + a[2] = z + a[3] = w + return a[-4] * 1000 + a[-3] * 100 + a[-2] * 10 + a[FOO] + """ + + assert_compile_failed( + lambda: get_contract_with_gas_estimation(array_constant_negative_accessor), ArrayIndexException + ) + array_negative_accessor = """ @external def test_array(x: int128, y: int128, z: int128, w: int128) -> int128: diff --git a/tests/functional/syntax/test_abi_decode.py b/tests/functional/syntax/test_abi_decode.py index f05ff429cd..a6665bb84c 100644 --- a/tests/functional/syntax/test_abi_decode.py +++ b/tests/functional/syntax/test_abi_decode.py @@ -26,7 +26,7 @@ def bar(j: String[32]) -> bool: @pytest.mark.parametrize("bad_code,exc", fail_list) -def test_abi_encode_fail(bad_code, exc): +def test_abi_decode_fail(bad_code, exc): with pytest.raises(exc): compiler.compile_code(bad_code) @@ -41,5 +41,5 @@ def foo(x: Bytes[32]) -> uint256: @pytest.mark.parametrize("good_code", valid_list) -def test_abi_encode_success(good_code): +def test_abi_decode_success(good_code): assert compiler.compile_code(good_code) is not None diff --git a/tests/functional/syntax/test_abs.py b/tests/functional/syntax/test_abs.py new file mode 100644 index 0000000000..314df672e0 --- /dev/null +++ b/tests/functional/syntax/test_abs.py @@ -0,0 +1,15 @@ +import pytest + +from vyper import compiler + +valid_list = [ + """ +FOO: constant(int256) = -3 +BAR: constant(int256) = abs(FOO) + """ +] + + +@pytest.mark.parametrize("code", valid_list) +def test_addmulmod_pass(code): + assert compiler.compile_code(code) is not None \ No newline at end of file diff --git a/tests/functional/syntax/test_addmulmod.py b/tests/functional/syntax/test_addmulmod.py index ddff4d3e01..f97d35ee16 100644 --- a/tests/functional/syntax/test_addmulmod.py +++ b/tests/functional/syntax/test_addmulmod.py @@ -1,5 +1,6 @@ import pytest +from vyper import compiler from vyper.exceptions import InvalidType fail_list = [ @@ -25,3 +26,24 @@ def foo() -> uint256: @pytest.mark.parametrize("code,exc", fail_list) def test_add_mod_fail(assert_compile_failed, get_contract, code, exc): assert_compile_failed(lambda: get_contract(code), exc) + + +valid_list = [ + """ +FOO: constant(uint256) = 3 +BAR: constant(uint256) = 5 +BAZ: constant(uint256) = 19 +BAX: constant(uint256) = uint256_addmod(FOO, BAR, BAZ) + """, + """ +FOO: constant(uint256) = 3 +BAR: constant(uint256) = 5 +BAZ: constant(uint256) = 19 +BAX: constant(uint256) = uint256_mulmod(FOO, BAR, BAZ) + """, +] + + +@pytest.mark.parametrize("code", valid_list) +def test_addmulmod_pass(code): + assert compiler.compile_code(code) is not None \ No newline at end of file diff --git a/tests/functional/syntax/test_ceil.py b/tests/functional/syntax/test_ceil.py new file mode 100644 index 0000000000..f5edb96369 --- /dev/null +++ b/tests/functional/syntax/test_ceil.py @@ -0,0 +1,18 @@ +import pytest + +from vyper.compiler import compile_code + +valid_list = [ + """ +BAR: constant(decimal) = 2.5 +FOO: constant(int256) = ceil(BAR) +@external +def foo(): + a: int256 = FOO + """ +] + + +@pytest.mark.parametrize("code", valid_list) +def test_ceil_good(code): + assert compile_code(code) is not None \ No newline at end of file diff --git a/tests/functional/syntax/test_floor.py b/tests/functional/syntax/test_floor.py new file mode 100644 index 0000000000..feaf3fb2b1 --- /dev/null +++ b/tests/functional/syntax/test_floor.py @@ -0,0 +1,18 @@ +import pytest + +from vyper.compiler import compile_code + +valid_list = [ + """ +BAR: constant(decimal) = 2.5 +FOO: constant(int256) = floor(BAR) +@external +def foo(): + a: int256 = FOO + """ +] + + +@pytest.mark.parametrize("code", valid_list) +def test_floor_good(code): + assert compile_code(code) is not None \ No newline at end of file diff --git a/tests/functional/syntax/test_for_range.py b/tests/functional/syntax/test_for_range.py index e6f35c1d2d..1fbe2c95f1 100644 --- a/tests/functional/syntax/test_for_range.py +++ b/tests/functional/syntax/test_for_range.py @@ -1,7 +1,7 @@ import pytest from vyper import compiler -from vyper.exceptions import StructureException +from vyper.exceptions import InvalidType, StructureException, TypeMismatch fail_list = [ ( @@ -16,6 +16,15 @@ def foo(): ( """ @external +def bar(): + for i in range(1,2,bound=0): + pass + """, + StructureException, + ), + ( + """ +@external def bar(): for i in range(1,2,bound=2): pass @@ -32,6 +41,47 @@ def bar(): """, StructureException, ), + ( + """ +@external +def bar(): + x:uint256 = 1 + y:uint256 = 2 + for i in range(x,y+1): + pass + """, + StructureException, + ), + ( + """ +@external +def bar(): + x:uint256 = 1 + for i in range(x,x+0): + pass + """, + StructureException, + ), + ( + """ +@external +def bar(x: uint256): + for i in range(3, x): + pass + """, + InvalidType, + ), + ( + """ +FOO: constant(int128) = 3 +BAR: constant(uint256) = 7 +@external +def foo(): + for i in range(FOO, BAR): + pass + """, + TypeMismatch, + ), ] diff --git a/tests/functional/syntax/test_len.py b/tests/functional/syntax/test_len.py index bbde7e4897..6867ceda63 100644 --- a/tests/functional/syntax/test_len.py +++ b/tests/functional/syntax/test_len.py @@ -39,6 +39,14 @@ def foo(inp: Bytes[10]) -> uint256: def foo(inp: String[10]) -> uint256: return len(inp) """, + """ +BAR: constant(String[5]) = "vyper" +FOO: constant(uint256) = len(BAR) +@external +def foo() -> uint256: + a: uint256 = FOO + return a + """, ] diff --git a/tests/functional/syntax/test_minmax.py b/tests/functional/syntax/test_minmax.py index 2ad3d363f1..971078923b 100644 --- a/tests/functional/syntax/test_minmax.py +++ b/tests/functional/syntax/test_minmax.py @@ -1,5 +1,6 @@ import pytest +from vyper import compiler from vyper.exceptions import InvalidType, TypeMismatch fail_list = [ @@ -25,3 +26,22 @@ def foo(): @pytest.mark.parametrize("bad_code,exc", fail_list) def test_block_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_code, exc): assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code), exc) + + +valid_list = [ + """ +FOO: constant(uint256) = 123 +BAR: constant(uint256) = 456 +BAZ: constant(uint256) = min(FOO, BAR) + """, + """ +FOO: constant(uint256) = 123 +BAR: constant(uint256) = 456 +BAZ: constant(uint256) = max(FOO, BAR) + """, +] + + +@pytest.mark.parametrize("good_code", valid_list) +def test_block_success(good_code): + assert compiler.compile_code(good_code) is not None \ No newline at end of file diff --git a/tests/functional/syntax/test_powmod.py b/tests/functional/syntax/test_powmod.py new file mode 100644 index 0000000000..a5bf0787b4 --- /dev/null +++ b/tests/functional/syntax/test_powmod.py @@ -0,0 +1,16 @@ +import pytest + +from vyper import compiler + +valid_list = [ + """ +FOO: constant(uint256) = 3 +BAR: constant(uint256) = 5 +BAZ: constant(uint256) = pow_mod256(FOO, BAR) + """ +] + + +@pytest.mark.parametrize("code", valid_list) +def test_powmod_pass(code): + assert compiler.compile_code(code) is not None \ No newline at end of file diff --git a/tests/functional/syntax/test_raw_call.py b/tests/functional/syntax/test_raw_call.py index b1286e7a8e..4ff058ba40 100644 --- a/tests/functional/syntax/test_raw_call.py +++ b/tests/functional/syntax/test_raw_call.py @@ -90,6 +90,20 @@ def foo(): value=self.balance - self.balances[0] ) """, + # test constants + """ +OUTSIZE: constant(uint256) = 4 +REVERT_ON_FAILURE: constant(bool) = True +@external +def foo(): + x: Bytes[9] = raw_call( + 0x1234567890123456789012345678901234567890, + b"cow", + max_outsize=OUTSIZE, + gas=595757, + revert_on_failure=REVERT_ON_FAILURE + ) + """, ] diff --git a/tests/functional/syntax/test_uint2str.py b/tests/functional/syntax/test_uint2str.py new file mode 100644 index 0000000000..ad6e20e1d0 --- /dev/null +++ b/tests/functional/syntax/test_uint2str.py @@ -0,0 +1,15 @@ +import pytest + +from vyper import compiler + +valid_list = [ + """ +FOO: constant(uint256) = 3 +BAR: constant(String[78]) = uint2str(FOO) + """ +] + + +@pytest.mark.parametrize("code", valid_list) +def test_addmulmod_pass(code): + assert compiler.compile_code(code) is not None \ No newline at end of file diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 4663676779..474dd4f721 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -651,7 +651,7 @@ def check_kwargable(node: vy_ast.VyperNode) -> bool: return True value_type = get_expr_info(node) - return value_type.is_runtime_constant + return value_type.is_runtime_constant or value_type.is_compile_time_constant def _check_literal(node: vy_ast.VyperNode) -> bool: From cd5f6ccc2fabc2ccfcad6ccafedc747053a277ee Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 15 Nov 2023 10:59:48 +0800 Subject: [PATCH 052/120] fix builtins wip --- vyper/builtins/functions.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 9240df0f21..aae7b07b11 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -468,7 +468,7 @@ class Len(BuiltinFunction): def evaluate(self, node): validate_call_args(node, 1) - arg = node.args[0] + arg = node.args[0]._metadata.get("folded_value") if isinstance(arg, (vy_ast.Str, vy_ast.Bytes)): length = len(arg.value) elif isinstance(arg, vy_ast.Hex): @@ -606,7 +606,6 @@ class Keccak256(BuiltinFunction): def evaluate(self, node): validate_call_args(node, 1) value = node.args[0]._metadata.get("folded_value") - if isinstance(value, vy_ast.Bytes): value = value.value elif isinstance(value, vy_ast.Str): @@ -654,13 +653,14 @@ class Sha256(BuiltinFunction): def evaluate(self, node): validate_call_args(node, 1) - if isinstance(node.args[0], vy_ast.Bytes): - value = node.args[0].value - elif isinstance(node.args[0], vy_ast.Str): - value = node.args[0].value.encode() - elif isinstance(node.args[0], vy_ast.Hex): - length = len(node.args[0].value) // 2 - 1 - value = int(node.args[0].value, 16).to_bytes(length, "big") + value = node.args[0]._metadata.get("folded_value") + if isinstance(value, vy_ast.Bytes): + value = value.value + elif isinstance(value, vy_ast.Str): + value = value.value.encode() + elif isinstance(value, vy_ast.Hex): + length = len(value.value) // 2 - 1 + value = int(value.value, 16).to_bytes(length, "big") else: raise UnfoldableNode @@ -1556,10 +1556,11 @@ class PowMod256(BuiltinFunction): def evaluate(self, node): validate_call_args(node, 2) - if next((i for i in node.args if not isinstance(i, vy_ast.Int)), None): + values = [i._metadata.get("folded_value") for i in node.args] + if any(not isinstance(i, vy_ast.Int) for i in values): raise UnfoldableNode - left, right = node.args + left, right = values if left.value < 0 or right.value < 0: raise UnfoldableNode @@ -1579,10 +1580,11 @@ class Abs(BuiltinFunction): def evaluate(self, node): validate_call_args(node, 1) - if not isinstance(node.args[0], vy_ast.Int): + value = node.args[0]._metadata.get("folded_value") + if not isinstance(value, vy_ast.Int): raise UnfoldableNode - value = node.args[0].value + value = value.value if not SizeLimits.MIN_INT256 <= value <= SizeLimits.MAX_INT256: raise OverflowException("Literal is outside of allowable range for int256") value = abs(value) @@ -2114,10 +2116,11 @@ def fetch_call_return(self, node): def evaluate(self, node): validate_call_args(node, 1) - if not isinstance(node.args[0], vy_ast.Int): + value = node.args[0]._metadata.get("folded_value") + if not isinstance(value, vy_ast.Int): raise UnfoldableNode - value = str(node.args[0].value) + value = str(value.value) return vy_ast.Str.from_node(node, value=value) def infer_arg_types(self, node, expected_return_typ=None): From b0b39e478c95361f9f610eb2ebed9e38270b6617 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 15 Nov 2023 10:59:56 +0800 Subject: [PATCH 053/120] add raw ast output --- vyper/cli/vyper_compile.py | 5 +++-- vyper/cli/vyper_json.py | 2 ++ vyper/compiler/__init__.py | 3 ++- vyper/compiler/output.py | 10 +++++++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/vyper/cli/vyper_compile.py b/vyper/cli/vyper_compile.py index 82eba63f32..e965d9d4c4 100755 --- a/vyper/cli/vyper_compile.py +++ b/vyper/cli/vyper_compile.py @@ -33,7 +33,8 @@ devdoc - Natspec developer documentation combined_json - All of the above format options combined as single JSON output layout - Storage layout of a Vyper contract -ast - AST in JSON format +ast - Annotated AST in JSON format +raw_ast - Unannotated AST in JSON format interface - Vyper interface of a contract external_interface - External interface of a contract, used for outside contract calls opcodes - List of opcodes as a string @@ -239,7 +240,7 @@ def compile_files( output_formats = combined_json_outputs show_version = True - translate_map = {"abi_python": "abi", "json": "abi", "ast": "ast_dict", "ir_json": "ir_dict"} + translate_map = {"abi_python": "abi", "json": "abi", "ast": "ast_dict", "raw_ast": "raw_ast_dict", "ir_json": "ir_dict"} final_formats = [translate_map.get(i, i) for i in output_formats] if storage_layout_paths: diff --git a/vyper/cli/vyper_json.py b/vyper/cli/vyper_json.py index 2720f20d23..df924d4d52 100755 --- a/vyper/cli/vyper_json.py +++ b/vyper/cli/vyper_json.py @@ -323,6 +323,8 @@ def format_to_output_dict(compiler_data: dict) -> dict: output_dict["sources"][path] = {"id": data["source_id"]} if "ast_dict" in data: output_dict["sources"][path]["ast"] = data["ast_dict"]["ast"] + if "raw_ast_dict" in data: + output_dict["sources"][path]["raw_ast"] = data["raw_ast_dict"]["ast"] name = PurePath(path).stem output_dict["contracts"][path] = {name: {}} diff --git a/vyper/compiler/__init__.py b/vyper/compiler/__init__.py index 62ea05b243..03d44cc39b 100644 --- a/vyper/compiler/__init__.py +++ b/vyper/compiler/__init__.py @@ -13,7 +13,8 @@ OUTPUT_FORMATS = { # requires vyper_module - "ast_dict": output.build_ast_dict, + "raw_ast_dict": output.build_ast_dict, + "ast_dict": output.build_annotated_ast_dict, "layout": output.build_layout_output, # requires global_ctx "devdoc": output.build_devdoc, diff --git a/vyper/compiler/output.py b/vyper/compiler/output.py index 643d8ebbd2..6f00197569 100644 --- a/vyper/compiler/output.py +++ b/vyper/compiler/output.py @@ -17,11 +17,19 @@ def build_ast_dict(compiler_data: CompilerData) -> dict: ast_dict = { "contract_name": str(compiler_data.contract_path), - "ast": ast_to_dict(compiler_data.vyper_module_annotated), + "ast": ast_to_dict(compiler_data.vyper_module), } return ast_dict +def build_annotated_ast_dict(compiler_data: CompilerData) -> dict: + annotated_ast_dict = { + "contract_name": str(compiler_data.contract_path), + "ast": ast_to_dict(compiler_data.vyper_module_annotated), + } + return annotated_ast_dict + + def build_devdoc(compiler_data: CompilerData) -> dict: userdoc, devdoc = parse_natspec(compiler_data.vyper_module_folded) return devdoc From 55a125edc414da217f88ea07177a18d7c17fa3c4 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 15 Nov 2023 12:49:06 +0800 Subject: [PATCH 054/120] remove dead code --- vyper/ast/expansion.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/vyper/ast/expansion.py b/vyper/ast/expansion.py index 5471b971a4..43807f7177 100644 --- a/vyper/ast/expansion.py +++ b/vyper/ast/expansion.py @@ -5,22 +5,6 @@ from vyper.semantics.types.function import ContractFunctionT -def expand_annotated_ast(vyper_module: vy_ast.Module) -> None: - """ - Perform expansion / simplification operations on an annotated Vyper AST. - - This pass uses annotated type information to modify the AST, simplifying - logic and expanding subtrees to reduce the compexity during codegen. - - Arguments - --------- - vyper_module : Module - Top-level Vyper AST node that has been type-checked and annotated. - """ - generate_public_variable_getters(vyper_module) - remove_unused_statements(vyper_module) - - def generate_public_variable_getters(vyper_module: vy_ast.Module) -> None: """ Create getter functions for public variables. From c4805210c3b7b7b68aca096a98f75a06dca74faf Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 15 Nov 2023 12:49:17 +0800 Subject: [PATCH 055/120] add type propagation for subscript folding --- vyper/ast/folding.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vyper/ast/folding.py b/vyper/ast/folding.py index 17ae0a9da7..bd845dd1a7 100644 --- a/vyper/ast/folding.py +++ b/vyper/ast/folding.py @@ -83,6 +83,8 @@ def replace_subscripts(vyper_module: vy_ast.Module) -> int: except UnfoldableNode: continue + new_node._metadata["type"] = node._metadata["type"] + changed_nodes += 1 vyper_module.replace_in_tree(node, new_node) @@ -116,10 +118,11 @@ def replace_builtin_functions(vyper_module: vy_ast.Module) -> int: continue try: new_node = func.evaluate(node) # type: ignore - new_node._metadata["type"] = node._metadata["type"] except UnfoldableNode: continue + new_node._metadata["type"] = node._metadata["type"] + changed_nodes += 1 vyper_module.replace_in_tree(node, new_node) @@ -168,7 +171,7 @@ def _replace(old_node, new_node, type_): new_node._metadata["type"] = type_ return new_node elif isinstance(new_node, vy_ast.List): - base_type = type_.value_type if type_ else None + base_type = type_.value_type list_values = [_replace(old_node, i, type_=base_type) for i in new_node.elements] new_node = new_node.from_node(old_node, elements=list_values) new_node._metadata["type"] = type_ From 6de0e81df085e6a929d875680f5e5f8ce8b6b546 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 15 Nov 2023 12:51:24 +0800 Subject: [PATCH 056/120] rename to always_folded_before_codegen --- vyper/builtins/functions.py | 4 ++-- vyper/semantics/analysis/local.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index aae7b07b11..c89d776b91 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -112,7 +112,7 @@ class FoldedFunction(BuiltinFunction): # this flag is used for `check_kwargable` in semantics validation. _kwargable = True # Skip annotation of builtins if it will be folded before codegen - _is_folded_before_codegen = True + _always_folded_before_codegen = True class TypenameFoldedFunction(FoldedFunction): @@ -2287,7 +2287,7 @@ class Empty(TypenameFoldedFunction): _id = "empty" # Since `empty` is not folded in the AST, `is_folded` is set to False # so that it will be properly annotated. - _is_folded_before_codegen = False + _always_folded_before_codegen = False def fetch_call_return(self, node): type_ = self.infer_arg_types(node)[0].typedef diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index e6c931e4fb..d91a270d03 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -684,7 +684,7 @@ def visit_Call(self, node: vy_ast.Call, typ: VyperType) -> None: for arg, arg_type in zip(node.args, call_type.arg_types): self.visit(arg, arg_type) else: - if getattr(call_type, "_is_folded_before_codegen", False): + if getattr(call_type, "_always_folded_before_codegen", False): return # builtin functions From 6a0ca23817623f9628d16d323c5fd8914498bfec Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:04:49 +0800 Subject: [PATCH 057/120] introduce constancy --- vyper/semantics/analysis/base.py | 27 ++++++++++++--------------- vyper/semantics/analysis/local.py | 6 +++--- vyper/semantics/analysis/module.py | 13 ++++++++++--- vyper/semantics/analysis/utils.py | 16 ++++++---------- vyper/semantics/environment.py | 4 ++-- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/vyper/semantics/analysis/base.py b/vyper/semantics/analysis/base.py index f38c39913f..bd49eadc7a 100644 --- a/vyper/semantics/analysis/base.py +++ b/vyper/semantics/analysis/base.py @@ -93,6 +93,12 @@ def from_abi(cls, abi_dict: Dict) -> "StateMutability": # specifying a state mutability modifier at all. Do the same here. +class Constancy(_StringEnum): + MUTABLE = _StringEnum.auto() + RUNTIME_CONSTANT = _StringEnum.auto() + COMPILE_TIME_CONSTANT = _StringEnum.auto() + + class DataPosition: _location: DataLocation @@ -159,8 +165,7 @@ class VarInfo: typ: VyperType location: DataLocation = DataLocation.UNSET - is_compile_time_constant: bool = False - is_runtime_constant: bool = False + constancy: Constancy = Constancy.MUTABLE is_public: bool = False is_immutable: bool = False is_transient: bool = False @@ -193,18 +198,11 @@ class ExprInfo: typ: VyperType var_info: Optional[VarInfo] = None location: DataLocation = DataLocation.UNSET - is_compile_time_constant: bool = False - is_runtime_constant: bool = False + constancy: Constancy = Constancy.MUTABLE is_immutable: bool = False def __post_init__(self): - should_match = ( - "typ", - "location", - "is_compile_time_constant", - "is_runtime_constant", - "is_immutable", - ) + should_match = ("typ", "location", "constancy", "is_immutable") if self.var_info is not None: for attr in should_match: if getattr(self.var_info, attr) != getattr(self, attr): @@ -216,8 +214,7 @@ def from_varinfo(cls, var_info: VarInfo) -> "ExprInfo": var_info.typ, var_info=var_info, location=var_info.location, - is_compile_time_constant=var_info.is_compile_time_constant, - is_runtime_constant=var_info.is_runtime_constant, + constancy=var_info.constancy, is_immutable=var_info.is_immutable, ) @@ -225,7 +222,7 @@ def copy_with_type(self, typ: VyperType) -> "ExprInfo": """ Return a copy of the ExprInfo but with the type set to something else """ - to_copy = ("location", "is_compile_time_constant", "is_runtime_constant", "is_immutable") + to_copy = ("location", "constancy", "is_immutable") fields = {k: getattr(self, k) for k in to_copy} return self.__class__(typ=typ, **fields) @@ -249,7 +246,7 @@ def validate_modification(self, node: vy_ast.VyperNode, mutability: StateMutabil if self.location == DataLocation.CALLDATA: raise ImmutableViolation("Cannot write to calldata", node) - if self.is_compile_time_constant: + if self.constancy == Constancy.COMPILE_TIME_CONSTANT: raise ImmutableViolation("Constant value cannot be written to", node) if self.is_immutable: if node.get_ancestor(vy_ast.FunctionDef).get("name") != "__init__": diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index d91a270d03..ebf5ff99ba 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -19,7 +19,7 @@ VariableDeclarationException, VyperException, ) -from vyper.semantics.analysis.base import VarInfo +from vyper.semantics.analysis.base import Constancy, VarInfo from vyper.semantics.analysis.common import VyperNodeVisitorBase from vyper.semantics.analysis.utils import ( get_common_types, @@ -197,7 +197,7 @@ def __init__( arg.typ, location=location, is_immutable=is_immutable, - is_runtime_constant=is_immutable, + constancy=Constancy.RUNTIME_CONSTANT, ) for node in fn_node.body: @@ -484,7 +484,7 @@ def visit_For(self, node): with self.namespace.enter_scope(): try: self.namespace[iter_name] = VarInfo( - possible_target_type, is_compile_time_constant=True + possible_target_type, constancy=Constancy.COMPILE_TIME_CONSTANT ) except VyperException as exc: raise exc.with_annotation(node) from None diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index c7f3dca671..c9daa0f807 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -18,7 +18,7 @@ VariableDeclarationException, VyperException, ) -from vyper.semantics.analysis.base import VarInfo +from vyper.semantics.analysis.base import Constancy, VarInfo from vyper.semantics.analysis.common import VyperNodeVisitorBase from vyper.semantics.analysis.local import ExprVisitor from vyper.semantics.analysis.utils import check_constant, validate_expected_type @@ -186,6 +186,14 @@ def visit_VariableDecl(self, node): else DataLocation.STORAGE ) + constancy = ( + Constancy.RUNTIME_CONSTANT + if node.is_immutable + else Constancy.COMPILE_TIME_CONSTANT + if node.is_constant + else Constancy.MUTABLE + ) + type_ = type_from_annotation(node.annotation, data_loc) if node.is_transient and not version_check(begin="cancun"): @@ -195,10 +203,9 @@ def visit_VariableDecl(self, node): type_, decl_node=node, location=data_loc, - is_compile_time_constant=node.is_constant, + constancy=constancy, is_public=node.is_public, is_immutable=node.is_immutable, - is_runtime_constant=node.is_immutable, is_transient=node.is_transient, ) node.target._metadata["varinfo"] = var_info # TODO maybe put this in the global namespace diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 474dd4f721..8ffe0df454 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -17,7 +17,7 @@ ZeroDivisionException, ) from vyper.semantics import types -from vyper.semantics.analysis.base import ExprInfo, VarInfo +from vyper.semantics.analysis.base import Constancy, ExprInfo, VarInfo from vyper.semantics.analysis.levenshtein_utils import get_levenshtein_error_suggestions from vyper.semantics.namespace import get_namespace from vyper.semantics.types.base import TYPE_T, VyperType @@ -91,17 +91,13 @@ def get_expr_info(self, node: vy_ast.VyperNode) -> ExprInfo: # kludge! for validate_modification in local analysis of Assign types = [self.get_expr_info(n) for n in node.elements] location = sorted((i.location for i in types), key=lambda k: k.value)[-1] - is_compile_time_constant = any( - (getattr(i, "is_compile_time_constant", False) for i in types) - ) - is_runtime_constant = any((getattr(i, "is_runtime_constant", False) for i in types)) + constancy = sorted((i.constancy for i in types), key=lambda k: k.value)[-1] is_immutable = any((getattr(i, "is_immutable", False) for i in types)) return ExprInfo( t, location=location, - is_compile_time_constant=is_compile_time_constant, - is_runtime_constant=is_runtime_constant, + constancy=constancy, is_immutable=is_immutable, ) @@ -202,7 +198,7 @@ def _raise_invalid_reference(name, node): if isinstance(s, VyperType): # ex. foo.bar(). bar() is a ContractFunctionT return [s] - if is_self_reference and (s.is_compile_time_constant or s.is_immutable): + if is_self_reference and s.constancy >= Constancy.RUNTIME_CONSTANT: _raise_invalid_reference(name, node) # general case. s is a VarInfo, e.g. self.foo return [s.typ] @@ -651,7 +647,7 @@ def check_kwargable(node: vy_ast.VyperNode) -> bool: return True value_type = get_expr_info(node) - return value_type.is_runtime_constant or value_type.is_compile_time_constant + return value_type.constancy >= Constancy.RUNTIME_CONSTANT def _check_literal(node: vy_ast.VyperNode) -> bool: @@ -694,4 +690,4 @@ def check_constant(node: vy_ast.VyperNode) -> bool: return True value_type = get_expr_info(node) - return value_type.is_compile_time_constant + return value_type.constancy == Constancy.COMPILE_TIME_CONSTANT diff --git a/vyper/semantics/environment.py b/vyper/semantics/environment.py index 0eeb803eb7..09006effa6 100644 --- a/vyper/semantics/environment.py +++ b/vyper/semantics/environment.py @@ -1,6 +1,6 @@ from typing import Dict -from vyper.semantics.analysis.base import VarInfo +from vyper.semantics.analysis.base import Constancy, VarInfo from vyper.semantics.types import AddressT, BytesT, VyperType from vyper.semantics.types.shortcuts import BYTES32_T, UINT256_T @@ -52,7 +52,7 @@ def get_constant_vars() -> Dict: """ result = {} for k, v in CONSTANT_ENVIRONMENT_VARS.items(): - result[k] = VarInfo(v, is_runtime_constant=True) + result[k] = VarInfo(v, constancy=Constancy.RUNTIME_CONSTANT) return result From d13eabbb7bfc06382f13eb1739f810516e710a6f Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:04:56 +0800 Subject: [PATCH 058/120] fix lint --- tests/functional/builtins/codegen/test_keccak256.py | 2 +- tests/functional/builtins/codegen/test_sha256.py | 2 +- tests/functional/codegen/types/test_dynamic_array.py | 5 +++-- tests/functional/syntax/test_abs.py | 2 +- tests/functional/syntax/test_addmulmod.py | 2 +- tests/functional/syntax/test_ceil.py | 2 +- tests/functional/syntax/test_floor.py | 2 +- tests/functional/syntax/test_minmax.py | 2 +- tests/functional/syntax/test_powmod.py | 2 +- tests/functional/syntax/test_uint2str.py | 2 +- vyper/cli/vyper_compile.py | 8 +++++++- 11 files changed, 19 insertions(+), 12 deletions(-) diff --git a/tests/functional/builtins/codegen/test_keccak256.py b/tests/functional/builtins/codegen/test_keccak256.py index e9a89a06a1..3b0b9f2018 100644 --- a/tests/functional/builtins/codegen/test_keccak256.py +++ b/tests/functional/builtins/codegen/test_keccak256.py @@ -110,4 +110,4 @@ def foo() -> bytes32: return x """ c = get_contract_with_gas_estimation(code) - assert "0x" + c.foo().hex() == keccak(str_val.encode()).hex() \ No newline at end of file + assert "0x" + c.foo().hex() == keccak(str_val.encode()).hex() diff --git a/tests/functional/builtins/codegen/test_sha256.py b/tests/functional/builtins/codegen/test_sha256.py index e9dd8736c0..8e1b89bd31 100644 --- a/tests/functional/builtins/codegen/test_sha256.py +++ b/tests/functional/builtins/codegen/test_sha256.py @@ -106,4 +106,4 @@ def foo() -> bytes32: return x """ c = get_contract_with_gas_estimation(code) - assert c.foo() == hashlib.sha256(str_val.encode()).digest() \ No newline at end of file + assert c.foo() == hashlib.sha256(str_val.encode()).digest() diff --git a/tests/functional/codegen/types/test_dynamic_array.py b/tests/functional/codegen/types/test_dynamic_array.py index f463f8d92b..f330c8f5e4 100644 --- a/tests/functional/codegen/types/test_dynamic_array.py +++ b/tests/functional/codegen/types/test_dynamic_array.py @@ -328,9 +328,10 @@ def test_array(x: int128, y: int128, z: int128, w: int128) -> int128: """ assert_compile_failed( - lambda: get_contract_with_gas_estimation(array_constant_negative_accessor), ArrayIndexException + lambda: get_contract_with_gas_estimation(array_constant_negative_accessor), + ArrayIndexException, ) - + array_negative_accessor = """ @external def test_array(x: int128, y: int128, z: int128, w: int128) -> int128: diff --git a/tests/functional/syntax/test_abs.py b/tests/functional/syntax/test_abs.py index 314df672e0..ee207c2d5a 100644 --- a/tests/functional/syntax/test_abs.py +++ b/tests/functional/syntax/test_abs.py @@ -12,4 +12,4 @@ @pytest.mark.parametrize("code", valid_list) def test_addmulmod_pass(code): - assert compiler.compile_code(code) is not None \ No newline at end of file + assert compiler.compile_code(code) is not None diff --git a/tests/functional/syntax/test_addmulmod.py b/tests/functional/syntax/test_addmulmod.py index f97d35ee16..7be87ddc97 100644 --- a/tests/functional/syntax/test_addmulmod.py +++ b/tests/functional/syntax/test_addmulmod.py @@ -46,4 +46,4 @@ def test_add_mod_fail(assert_compile_failed, get_contract, code, exc): @pytest.mark.parametrize("code", valid_list) def test_addmulmod_pass(code): - assert compiler.compile_code(code) is not None \ No newline at end of file + assert compiler.compile_code(code) is not None diff --git a/tests/functional/syntax/test_ceil.py b/tests/functional/syntax/test_ceil.py index f5edb96369..53608ebea4 100644 --- a/tests/functional/syntax/test_ceil.py +++ b/tests/functional/syntax/test_ceil.py @@ -15,4 +15,4 @@ def foo(): @pytest.mark.parametrize("code", valid_list) def test_ceil_good(code): - assert compile_code(code) is not None \ No newline at end of file + assert compile_code(code) is not None diff --git a/tests/functional/syntax/test_floor.py b/tests/functional/syntax/test_floor.py index feaf3fb2b1..7b7f960a86 100644 --- a/tests/functional/syntax/test_floor.py +++ b/tests/functional/syntax/test_floor.py @@ -15,4 +15,4 @@ def foo(): @pytest.mark.parametrize("code", valid_list) def test_floor_good(code): - assert compile_code(code) is not None \ No newline at end of file + assert compile_code(code) is not None diff --git a/tests/functional/syntax/test_minmax.py b/tests/functional/syntax/test_minmax.py index 971078923b..c7c1085811 100644 --- a/tests/functional/syntax/test_minmax.py +++ b/tests/functional/syntax/test_minmax.py @@ -44,4 +44,4 @@ def test_block_fail(assert_compile_failed, get_contract_with_gas_estimation, bad @pytest.mark.parametrize("good_code", valid_list) def test_block_success(good_code): - assert compiler.compile_code(good_code) is not None \ No newline at end of file + assert compiler.compile_code(good_code) is not None diff --git a/tests/functional/syntax/test_powmod.py b/tests/functional/syntax/test_powmod.py index a5bf0787b4..032f8b841f 100644 --- a/tests/functional/syntax/test_powmod.py +++ b/tests/functional/syntax/test_powmod.py @@ -13,4 +13,4 @@ @pytest.mark.parametrize("code", valid_list) def test_powmod_pass(code): - assert compiler.compile_code(code) is not None \ No newline at end of file + assert compiler.compile_code(code) is not None diff --git a/tests/functional/syntax/test_uint2str.py b/tests/functional/syntax/test_uint2str.py index ad6e20e1d0..392b7b952b 100644 --- a/tests/functional/syntax/test_uint2str.py +++ b/tests/functional/syntax/test_uint2str.py @@ -12,4 +12,4 @@ @pytest.mark.parametrize("code", valid_list) def test_addmulmod_pass(code): - assert compiler.compile_code(code) is not None \ No newline at end of file + assert compiler.compile_code(code) is not None diff --git a/vyper/cli/vyper_compile.py b/vyper/cli/vyper_compile.py index e965d9d4c4..86a63e80ad 100755 --- a/vyper/cli/vyper_compile.py +++ b/vyper/cli/vyper_compile.py @@ -240,7 +240,13 @@ def compile_files( output_formats = combined_json_outputs show_version = True - translate_map = {"abi_python": "abi", "json": "abi", "ast": "ast_dict", "raw_ast": "raw_ast_dict", "ir_json": "ir_dict"} + translate_map = { + "abi_python": "abi", + "json": "abi", + "ast": "ast_dict", + "raw_ast": "raw_ast_dict", + "ir_json": "ir_dict", + } final_formats = [translate_map.get(i, i) for i in output_formats] if storage_layout_paths: From 16b069cf548f09186a71cb9ce8cfb31956435219 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:13:54 +0800 Subject: [PATCH 059/120] rename to VariableConstancy --- vyper/semantics/analysis/base.py | 8 ++++---- vyper/semantics/analysis/local.py | 6 +++--- vyper/semantics/analysis/module.py | 8 ++++---- vyper/semantics/analysis/utils.py | 8 ++++---- vyper/semantics/environment.py | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/vyper/semantics/analysis/base.py b/vyper/semantics/analysis/base.py index bd49eadc7a..3cd346e532 100644 --- a/vyper/semantics/analysis/base.py +++ b/vyper/semantics/analysis/base.py @@ -93,7 +93,7 @@ def from_abi(cls, abi_dict: Dict) -> "StateMutability": # specifying a state mutability modifier at all. Do the same here. -class Constancy(_StringEnum): +class VariableConstancy(_StringEnum): MUTABLE = _StringEnum.auto() RUNTIME_CONSTANT = _StringEnum.auto() COMPILE_TIME_CONSTANT = _StringEnum.auto() @@ -165,7 +165,7 @@ class VarInfo: typ: VyperType location: DataLocation = DataLocation.UNSET - constancy: Constancy = Constancy.MUTABLE + constancy: VariableConstancy = VariableConstancy.MUTABLE is_public: bool = False is_immutable: bool = False is_transient: bool = False @@ -198,7 +198,7 @@ class ExprInfo: typ: VyperType var_info: Optional[VarInfo] = None location: DataLocation = DataLocation.UNSET - constancy: Constancy = Constancy.MUTABLE + constancy: VariableConstancy = VariableConstancy.MUTABLE is_immutable: bool = False def __post_init__(self): @@ -246,7 +246,7 @@ def validate_modification(self, node: vy_ast.VyperNode, mutability: StateMutabil if self.location == DataLocation.CALLDATA: raise ImmutableViolation("Cannot write to calldata", node) - if self.constancy == Constancy.COMPILE_TIME_CONSTANT: + if self.constancy == VariableConstancy.COMPILE_TIME_CONSTANT: raise ImmutableViolation("Constant value cannot be written to", node) if self.is_immutable: if node.get_ancestor(vy_ast.FunctionDef).get("name") != "__init__": diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index ebf5ff99ba..c3c4bc4fce 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -19,7 +19,7 @@ VariableDeclarationException, VyperException, ) -from vyper.semantics.analysis.base import Constancy, VarInfo +from vyper.semantics.analysis.base import VariableConstancy, VarInfo from vyper.semantics.analysis.common import VyperNodeVisitorBase from vyper.semantics.analysis.utils import ( get_common_types, @@ -197,7 +197,7 @@ def __init__( arg.typ, location=location, is_immutable=is_immutable, - constancy=Constancy.RUNTIME_CONSTANT, + constancy=VariableConstancy.RUNTIME_CONSTANT, ) for node in fn_node.body: @@ -484,7 +484,7 @@ def visit_For(self, node): with self.namespace.enter_scope(): try: self.namespace[iter_name] = VarInfo( - possible_target_type, constancy=Constancy.COMPILE_TIME_CONSTANT + possible_target_type, constancy=VariableConstancy.COMPILE_TIME_CONSTANT ) except VyperException as exc: raise exc.with_annotation(node) from None diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index c9daa0f807..120739a89d 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -18,7 +18,7 @@ VariableDeclarationException, VyperException, ) -from vyper.semantics.analysis.base import Constancy, VarInfo +from vyper.semantics.analysis.base import VariableConstancy, VarInfo from vyper.semantics.analysis.common import VyperNodeVisitorBase from vyper.semantics.analysis.local import ExprVisitor from vyper.semantics.analysis.utils import check_constant, validate_expected_type @@ -187,11 +187,11 @@ def visit_VariableDecl(self, node): ) constancy = ( - Constancy.RUNTIME_CONSTANT + VariableConstancy.RUNTIME_CONSTANT if node.is_immutable - else Constancy.COMPILE_TIME_CONSTANT + else VariableConstancy.COMPILE_TIME_CONSTANT if node.is_constant - else Constancy.MUTABLE + else VariableConstancy.MUTABLE ) type_ = type_from_annotation(node.annotation, data_loc) diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 8ffe0df454..49cf37c22f 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -17,7 +17,7 @@ ZeroDivisionException, ) from vyper.semantics import types -from vyper.semantics.analysis.base import Constancy, ExprInfo, VarInfo +from vyper.semantics.analysis.base import VariableConstancy, ExprInfo, VarInfo from vyper.semantics.analysis.levenshtein_utils import get_levenshtein_error_suggestions from vyper.semantics.namespace import get_namespace from vyper.semantics.types.base import TYPE_T, VyperType @@ -198,7 +198,7 @@ def _raise_invalid_reference(name, node): if isinstance(s, VyperType): # ex. foo.bar(). bar() is a ContractFunctionT return [s] - if is_self_reference and s.constancy >= Constancy.RUNTIME_CONSTANT: + if is_self_reference and s.constancy >= VariableConstancy.RUNTIME_CONSTANT: _raise_invalid_reference(name, node) # general case. s is a VarInfo, e.g. self.foo return [s.typ] @@ -647,7 +647,7 @@ def check_kwargable(node: vy_ast.VyperNode) -> bool: return True value_type = get_expr_info(node) - return value_type.constancy >= Constancy.RUNTIME_CONSTANT + return value_type.constancy >= VariableConstancy.RUNTIME_CONSTANT def _check_literal(node: vy_ast.VyperNode) -> bool: @@ -690,4 +690,4 @@ def check_constant(node: vy_ast.VyperNode) -> bool: return True value_type = get_expr_info(node) - return value_type.constancy == Constancy.COMPILE_TIME_CONSTANT + return value_type.constancy == VariableConstancy.COMPILE_TIME_CONSTANT diff --git a/vyper/semantics/environment.py b/vyper/semantics/environment.py index 09006effa6..937237ad0b 100644 --- a/vyper/semantics/environment.py +++ b/vyper/semantics/environment.py @@ -1,6 +1,6 @@ from typing import Dict -from vyper.semantics.analysis.base import Constancy, VarInfo +from vyper.semantics.analysis.base import VariableConstancy, VarInfo from vyper.semantics.types import AddressT, BytesT, VyperType from vyper.semantics.types.shortcuts import BYTES32_T, UINT256_T @@ -52,7 +52,7 @@ def get_constant_vars() -> Dict: """ result = {} for k, v in CONSTANT_ENVIRONMENT_VARS.items(): - result[k] = VarInfo(v, constancy=Constancy.RUNTIME_CONSTANT) + result[k] = VarInfo(v, constancy=VariableConstancy.RUNTIME_CONSTANT) return result From 2705f10a2f0299b135c1b8bd8f0cd5e81d45cbeb Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:15:09 +0800 Subject: [PATCH 060/120] fix lint --- vyper/semantics/analysis/utils.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 49cf37c22f..695bc8584c 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -17,7 +17,7 @@ ZeroDivisionException, ) from vyper.semantics import types -from vyper.semantics.analysis.base import VariableConstancy, ExprInfo, VarInfo +from vyper.semantics.analysis.base import ExprInfo, VariableConstancy, VarInfo from vyper.semantics.analysis.levenshtein_utils import get_levenshtein_error_suggestions from vyper.semantics.namespace import get_namespace from vyper.semantics.types.base import TYPE_T, VyperType @@ -94,12 +94,7 @@ def get_expr_info(self, node: vy_ast.VyperNode) -> ExprInfo: constancy = sorted((i.constancy for i in types), key=lambda k: k.value)[-1] is_immutable = any((getattr(i, "is_immutable", False) for i in types)) - return ExprInfo( - t, - location=location, - constancy=constancy, - is_immutable=is_immutable, - ) + return ExprInfo(t, location=location, constancy=constancy, is_immutable=is_immutable) # If it's a Subscript, propagate the subscriptable varinfo if isinstance(node, vy_ast.Subscript): From 5a51de394e8ed3f23b139353341597595ccc5378 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:24:57 +0800 Subject: [PATCH 061/120] use IntEnum --- vyper/semantics/analysis/base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vyper/semantics/analysis/base.py b/vyper/semantics/analysis/base.py index 3cd346e532..db19175cae 100644 --- a/vyper/semantics/analysis/base.py +++ b/vyper/semantics/analysis/base.py @@ -93,10 +93,10 @@ def from_abi(cls, abi_dict: Dict) -> "StateMutability": # specifying a state mutability modifier at all. Do the same here. -class VariableConstancy(_StringEnum): - MUTABLE = _StringEnum.auto() - RUNTIME_CONSTANT = _StringEnum.auto() - COMPILE_TIME_CONSTANT = _StringEnum.auto() +class VariableConstancy(enum.IntEnum): + MUTABLE = enum.auto() + RUNTIME_CONSTANT = enum.auto() + COMPILE_TIME_CONSTANT = enum.auto() class DataPosition: From 3078bcc92b0d44cd78d81450b0a51c232e475667 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 15 Nov 2023 15:37:49 +0800 Subject: [PATCH 062/120] fix more builtins --- vyper/builtins/functions.py | 40 ++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index c89d776b91..bf7555294b 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -725,14 +725,14 @@ class MethodID(FoldedFunction): def evaluate(self, node): validate_call_args(node, 1, ["output_type"]) - args = node.args - if not isinstance(args[0], vy_ast.Str): + value = node.args[0]._metadata.get("folded_value") + if not isinstance(value, vy_ast.Str): raise InvalidType("method id must be given as a literal string", args[0]) - if " " in args[0].value: + if " " in value.value: raise InvalidLiteral("Invalid function signature - no spaces allowed.") return_type = self.infer_kwarg_types(node) - value = method_id_int(args[0].value) + value = method_id_int(value.value) if return_type.compare_type(BYTES4_T): return vy_ast.Hex.from_node(node, value=hex(value)) @@ -1359,13 +1359,14 @@ def evaluate(self, node): self.__class__._warned = True validate_call_args(node, 2) - for arg in node.args: - if not isinstance(arg, vy_ast.Int): + values = [i._metadata.get("folded_value") for i in node.args] + for val, arg in zip(values, node.args): + if not isinstance(val, vy_ast.Int): raise UnfoldableNode - if arg.value < 0 or arg.value >= 2**256: + if val.value < 0 or val.value >= 2**256: raise InvalidLiteral("Value out of range for uint256", arg) - value = node.args[0].value & node.args[1].value + value = values[0].value & values[1].value return vy_ast.Int.from_node(node, value=value) @process_inputs @@ -1385,13 +1386,14 @@ def evaluate(self, node): self.__class__._warned = True validate_call_args(node, 2) - for arg in node.args: - if not isinstance(arg, vy_ast.Int): + values = [i._metadata.get("folded_value") for i in node.args] + for val, arg in zip(values, node.args): + if not isinstance(val, vy_ast.Int): raise UnfoldableNode - if arg.value < 0 or arg.value >= 2**256: + if val.value < 0 or val.value >= 2**256: raise InvalidLiteral("Value out of range for uint256", arg) - value = node.args[0].value | node.args[1].value + value = values[0].value | values[1].value return vy_ast.Int.from_node(node, value=value) @process_inputs @@ -1411,13 +1413,14 @@ def evaluate(self, node): self.__class__._warned = True validate_call_args(node, 2) - for arg in node.args: - if not isinstance(arg, vy_ast.Int): + values = [i._metadata.get("folded_value") for i in node.args] + for val, arg in zip(values, node.args): + if not isinstance(val, vy_ast.Int): raise UnfoldableNode - if arg.value < 0 or arg.value >= 2**256: + if val.value < 0 or val.value >= 2**256: raise InvalidLiteral("Value out of range for uint256", arg) - value = node.args[0].value ^ node.args[1].value + value = values[0].value ^ values[1].value return vy_ast.Int.from_node(node, value=value) @process_inputs @@ -1437,10 +1440,11 @@ def evaluate(self, node): self.__class__._warned = True validate_call_args(node, 1) - if not isinstance(node.args[0], vy_ast.Int): + value = node.args[0]._metadata.get("folded_value") + if not isinstance(value, vy_ast.Int): raise UnfoldableNode - value = node.args[0].value + value = value.value if value < 0 or value >= 2**256: raise InvalidLiteral("Value out of range for uint256", node.args[0]) From 1683fb834b05dca9d2e9f321ea787da66819f318 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 15 Nov 2023 15:37:54 +0800 Subject: [PATCH 063/120] add test --- tests/functional/syntax/test_method_id.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/functional/syntax/test_method_id.py diff --git a/tests/functional/syntax/test_method_id.py b/tests/functional/syntax/test_method_id.py new file mode 100644 index 0000000000..dc7b44dff9 --- /dev/null +++ b/tests/functional/syntax/test_method_id.py @@ -0,0 +1,19 @@ +import pytest + +from vyper import compiler + +valid_list = [ + """ +FOO: constant(String[5]) = "foo()" +BAR: constant(Bytes[4]) = method_id(FOO) + +@external +def foo(a: Bytes[4] = BAR): + pass + """ +] + + +@pytest.mark.parametrize("code", valid_list) +def test_addmulmod_pass(code): + assert compiler.compile_code(code) is not None From 28b02e7a401ab6cdec18b61f2e121154555788a2 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 15 Nov 2023 15:57:31 +0800 Subject: [PATCH 064/120] rename unannotated ast output option --- vyper/cli/vyper_compile.py | 2 +- vyper/cli/vyper_json.py | 2 -- vyper/compiler/__init__.py | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/vyper/cli/vyper_compile.py b/vyper/cli/vyper_compile.py index 86a63e80ad..4d68b7e5ce 100755 --- a/vyper/cli/vyper_compile.py +++ b/vyper/cli/vyper_compile.py @@ -244,7 +244,7 @@ def compile_files( "abi_python": "abi", "json": "abi", "ast": "ast_dict", - "raw_ast": "raw_ast_dict", + "unannotated_ast": "unannotated_ast_dict", "ir_json": "ir_dict", } final_formats = [translate_map.get(i, i) for i in output_formats] diff --git a/vyper/cli/vyper_json.py b/vyper/cli/vyper_json.py index df924d4d52..2720f20d23 100755 --- a/vyper/cli/vyper_json.py +++ b/vyper/cli/vyper_json.py @@ -323,8 +323,6 @@ def format_to_output_dict(compiler_data: dict) -> dict: output_dict["sources"][path] = {"id": data["source_id"]} if "ast_dict" in data: output_dict["sources"][path]["ast"] = data["ast_dict"]["ast"] - if "raw_ast_dict" in data: - output_dict["sources"][path]["raw_ast"] = data["raw_ast_dict"]["ast"] name = PurePath(path).stem output_dict["contracts"][path] = {name: {}} diff --git a/vyper/compiler/__init__.py b/vyper/compiler/__init__.py index 03d44cc39b..d67a4416c5 100644 --- a/vyper/compiler/__init__.py +++ b/vyper/compiler/__init__.py @@ -13,7 +13,7 @@ OUTPUT_FORMATS = { # requires vyper_module - "raw_ast_dict": output.build_ast_dict, + "unannotated_ast_dict": output.build_ast_dict, "ast_dict": output.build_annotated_ast_dict, "layout": output.build_layout_output, # requires global_ctx From 7b46c4eaa741b7590d238e1b2a2330f11fc40b87 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 15 Nov 2023 15:58:20 +0800 Subject: [PATCH 065/120] fix method id typo --- vyper/builtins/functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index bf7555294b..1f17d19641 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -727,7 +727,7 @@ def evaluate(self, node): value = node.args[0]._metadata.get("folded_value") if not isinstance(value, vy_ast.Str): - raise InvalidType("method id must be given as a literal string", args[0]) + raise InvalidType("method id must be given as a literal string", node.args[0]) if " " in value.value: raise InvalidLiteral("Invalid function signature - no spaces allowed.") From fc565d6d034c9ab2b732978bb1653f0de672574e Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 15 Nov 2023 16:39:14 +0800 Subject: [PATCH 066/120] fix help test --- vyper/cli/vyper_compile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/cli/vyper_compile.py b/vyper/cli/vyper_compile.py index 4d68b7e5ce..300d12956d 100755 --- a/vyper/cli/vyper_compile.py +++ b/vyper/cli/vyper_compile.py @@ -34,7 +34,7 @@ combined_json - All of the above format options combined as single JSON output layout - Storage layout of a Vyper contract ast - Annotated AST in JSON format -raw_ast - Unannotated AST in JSON format +unannotated_ast - Unannotated AST in JSON format interface - Vyper interface of a contract external_interface - External interface of a contract, used for outside contract calls opcodes - List of opcodes as a string From 7a13db25adc947dec971151fe62f78f78cde61e6 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 16 Nov 2023 17:52:31 +0800 Subject: [PATCH 067/120] refactor user defined const folding --- vyper/ast/folding.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/vyper/ast/folding.py b/vyper/ast/folding.py index bd845dd1a7..0863dba174 100644 --- a/vyper/ast/folding.py +++ b/vyper/ast/folding.py @@ -147,9 +147,6 @@ def replace_user_defined_constants(vyper_module: vy_ast.Module) -> int: changed_nodes = 0 for node in vyper_module.get_children(vy_ast.VariableDecl): - if not isinstance(node.target, vy_ast.Name): - # left-hand-side of assignment is not a variable - continue if not node.is_constant: # annotation is not wrapped in `constant(...)` continue From 8785e027cde23c8546066eaf1e5f8ec65bc07648 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 16 Nov 2023 17:55:44 +0800 Subject: [PATCH 068/120] add darray test --- tests/functional/syntax/test_dynamic_array.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/functional/syntax/test_dynamic_array.py b/tests/functional/syntax/test_dynamic_array.py index 0c23bf67da..46e74615a2 100644 --- a/tests/functional/syntax/test_dynamic_array.py +++ b/tests/functional/syntax/test_dynamic_array.py @@ -24,6 +24,14 @@ def foo(): """, StructureException, ), + ( + """ +@external +def foo(): + a: DynArray[uint256, FOO] = [1, 2, 3] + """, + StructureException, + ), ] From 926fef62bbe09c19bb5b645887e603026155424c Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 16 Nov 2023 18:07:39 +0800 Subject: [PATCH 069/120] combine check_kwargable and check_constant --- vyper/builtins/_signatures.py | 7 ++--- vyper/builtins/functions.py | 2 +- vyper/semantics/analysis/module.py | 4 +-- vyper/semantics/analysis/utils.py | 41 +++++------------------------- vyper/semantics/types/function.py | 6 ++--- 5 files changed, 16 insertions(+), 44 deletions(-) diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py index 44387095a3..6562ac9ac6 100644 --- a/vyper/builtins/_signatures.py +++ b/vyper/builtins/_signatures.py @@ -5,8 +5,9 @@ from vyper.codegen.expr import Expr from vyper.codegen.ir_node import IRnode from vyper.exceptions import CompilerPanic, TypeMismatch, UnfoldableNode, VyperException +from vyper.semantics.analysis.base import VariableConstancy from vyper.semantics.analysis.utils import ( - check_kwargable, + check_variable_constancy, get_exact_type_from_node, validate_expected_type, ) @@ -107,8 +108,8 @@ def _validate_arg_types(self, node): for kwarg in node.keywords: kwarg_settings = self._kwargs[kwarg.arg] - if kwarg_settings.require_literal and not check_kwargable(kwarg.value): - raise TypeMismatch("Value for kwarg must be a literal", kwarg.value) + if kwarg_settings.require_literal and not check_variable_constancy(kwarg.value, VariableConstancy.RUNTIME_CONSTANT): + raise TypeMismatch("Value must be literal or environment variable", kwarg.value) self._validate_single(kwarg.value, kwarg_settings.typ) # typecheck varargs. we don't have type info from the signature, diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 1f17d19641..e42521cacc 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -109,7 +109,7 @@ class FoldedFunction(BuiltinFunction): # Base class for nodes which should always be folded # Since foldable builtin functions are not folded before semantics validation, - # this flag is used for `check_kwargable` in semantics validation. + # this flag is used for `check_variable_constancy` in semantics validation. _kwargable = True # Skip annotation of builtins if it will be folded before codegen _always_folded_before_codegen = True diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index 120739a89d..6952bb1ee4 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -21,7 +21,7 @@ from vyper.semantics.analysis.base import VariableConstancy, VarInfo from vyper.semantics.analysis.common import VyperNodeVisitorBase from vyper.semantics.analysis.local import ExprVisitor -from vyper.semantics.analysis.utils import check_constant, validate_expected_type +from vyper.semantics.analysis.utils import check_variable_constancy, validate_expected_type from vyper.semantics.data_locations import DataLocation from vyper.semantics.namespace import Namespace, get_namespace from vyper.semantics.types import EnumT, EventT, InterfaceT, StructT @@ -242,7 +242,7 @@ def _validate_self_namespace(): if node.is_constant: if not node.value: raise VariableDeclarationException("Constant must be declared with a value", node) - if not check_constant(node.value): + if not check_variable_constancy(node.value, VariableConstancy.COMPILE_TIME_CONSTANT): raise StateAccessViolation("Value must be a literal", node.value) validate_expected_type(node.value, type_) diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 695bc8584c..3baa6b4464 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -616,35 +616,6 @@ def validate_unique_method_ids(functions: List) -> None: seen.add(method_id) -def check_kwargable(node: vy_ast.VyperNode) -> bool: - """ - Check if the given node can be used as a default arg - """ - if _check_literal(node): - return True - - if isinstance(node, vy_ast.BinOp): - return all(check_kwargable(i) for i in (node.left, node.right)) - - if isinstance(node, vy_ast.BoolOp): - return all(check_kwargable(i) for i in node.values) - - if isinstance(node, (vy_ast.Tuple, vy_ast.List)): - return all(check_kwargable(item) for item in node.elements) - - if isinstance(node, vy_ast.Call): - args = node.args - if len(args) == 1 and isinstance(args[0], vy_ast.Dict): - return all(check_kwargable(v) for v in args[0].values) - - call_type = get_exact_type_from_node(node.func) - if getattr(call_type, "_kwargable", False): - return True - - value_type = get_expr_info(node) - return value_type.constancy >= VariableConstancy.RUNTIME_CONSTANT - - def _check_literal(node: vy_ast.VyperNode) -> bool: """ Check if the given node is a literal value. @@ -659,7 +630,7 @@ def _check_literal(node: vy_ast.VyperNode) -> bool: return False -def check_constant(node: vy_ast.VyperNode) -> bool: +def check_variable_constancy(node: vy_ast.VyperNode, constancy: VariableConstancy) -> bool: """ Check if the given node is a literal or constant value. """ @@ -667,22 +638,22 @@ def check_constant(node: vy_ast.VyperNode) -> bool: return True if isinstance(node, vy_ast.BinOp): - return all(check_constant(i) for i in (node.left, node.right)) + return all(check_variable_constancy(i, constancy) for i in (node.left, node.right)) if isinstance(node, vy_ast.BoolOp): - return all(check_constant(i) for i in node.values) + return all(check_variable_constancy(i, constancy) for i in node.values) if isinstance(node, (vy_ast.Tuple, vy_ast.List)): - return all(check_constant(item) for item in node.elements) + return all(check_variable_constancy(item, constancy) for item in node.elements) if isinstance(node, vy_ast.Call): args = node.args if len(args) == 1 and isinstance(args[0], vy_ast.Dict): - return all(check_constant(v) for v in args[0].values) + return all(check_variable_constancy(v, constancy) for v in args[0].values) call_type = get_exact_type_from_node(node.func) if getattr(call_type, "_kwargable", False): return True value_type = get_expr_info(node) - return value_type.constancy == VariableConstancy.COMPILE_TIME_CONSTANT + return value_type.constancy >= constancy diff --git a/vyper/semantics/types/function.py b/vyper/semantics/types/function.py index 6aefaf575c..b2ed83b70c 100644 --- a/vyper/semantics/types/function.py +++ b/vyper/semantics/types/function.py @@ -16,8 +16,8 @@ StateAccessViolation, StructureException, ) -from vyper.semantics.analysis.base import FunctionVisibility, StateMutability, StorageSlot -from vyper.semantics.analysis.utils import check_kwargable, validate_expected_type +from vyper.semantics.analysis.base import FunctionVisibility, StateMutability, StorageSlot, VariableConstancy +from vyper.semantics.analysis.utils import check_variable_constancy, validate_expected_type from vyper.semantics.data_locations import DataLocation from vyper.semantics.types.base import KwargSettings, VyperType from vyper.semantics.types.primitives import BoolT @@ -320,7 +320,7 @@ def from_FunctionDef( positional_args.append(PositionalArg(argname, type_, ast_source=arg)) else: value = node.args.defaults[i - n_positional_args] - if not check_kwargable(value): + if not check_variable_constancy(value, VariableConstancy.RUNTIME_CONSTANT): raise StateAccessViolation( "Value must be literal or environment variable", value ) From 19b0102fa04dc0985b69b914477819222ca2bda9 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 16 Nov 2023 18:13:24 +0800 Subject: [PATCH 070/120] remove folded_before_codegen attribute --- vyper/builtins/functions.py | 5 ----- vyper/semantics/analysis/local.py | 3 --- 2 files changed, 8 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index e42521cacc..8f8713b59e 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -111,8 +111,6 @@ class FoldedFunction(BuiltinFunction): # Since foldable builtin functions are not folded before semantics validation, # this flag is used for `check_variable_constancy` in semantics validation. _kwargable = True - # Skip annotation of builtins if it will be folded before codegen - _always_folded_before_codegen = True class TypenameFoldedFunction(FoldedFunction): @@ -2289,9 +2287,6 @@ def build_IR(self, expr, args, kwargs, context): class Empty(TypenameFoldedFunction): _id = "empty" - # Since `empty` is not folded in the AST, `is_folded` is set to False - # so that it will be properly annotated. - _always_folded_before_codegen = False def fetch_call_return(self, node): type_ = self.infer_arg_types(node)[0].typedef diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index c3c4bc4fce..342d1aa243 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -684,9 +684,6 @@ def visit_Call(self, node: vy_ast.Call, typ: VyperType) -> None: for arg, arg_type in zip(node.args, call_type.arg_types): self.visit(arg, arg_type) else: - if getattr(call_type, "_always_folded_before_codegen", False): - return - # builtin functions arg_types = call_type.infer_arg_types(node, typ) # `infer_arg_types` already calls `validate_expected_type` From 596958a1dc029534b00ad5d4b7a5175b3dcf6930 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 16 Nov 2023 18:13:36 +0800 Subject: [PATCH 071/120] fix lint --- vyper/builtins/_signatures.py | 4 +++- vyper/semantics/types/function.py | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py index 6562ac9ac6..df6fa68425 100644 --- a/vyper/builtins/_signatures.py +++ b/vyper/builtins/_signatures.py @@ -108,7 +108,9 @@ def _validate_arg_types(self, node): for kwarg in node.keywords: kwarg_settings = self._kwargs[kwarg.arg] - if kwarg_settings.require_literal and not check_variable_constancy(kwarg.value, VariableConstancy.RUNTIME_CONSTANT): + if kwarg_settings.require_literal and not check_variable_constancy( + kwarg.value, VariableConstancy.RUNTIME_CONSTANT + ): raise TypeMismatch("Value must be literal or environment variable", kwarg.value) self._validate_single(kwarg.value, kwarg_settings.typ) diff --git a/vyper/semantics/types/function.py b/vyper/semantics/types/function.py index b2ed83b70c..39b60faf3c 100644 --- a/vyper/semantics/types/function.py +++ b/vyper/semantics/types/function.py @@ -16,7 +16,12 @@ StateAccessViolation, StructureException, ) -from vyper.semantics.analysis.base import FunctionVisibility, StateMutability, StorageSlot, VariableConstancy +from vyper.semantics.analysis.base import ( + FunctionVisibility, + StateMutability, + StorageSlot, + VariableConstancy, +) from vyper.semantics.analysis.utils import check_variable_constancy, validate_expected_type from vyper.semantics.data_locations import DataLocation from vyper.semantics.types.base import KwargSettings, VyperType From 19f5c7f9ccf59c16ab15c0cf264c49a286816550 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 16 Nov 2023 18:53:15 +0800 Subject: [PATCH 072/120] fix methodid --- vyper/builtins/functions.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 8f8713b59e..6b09c479a1 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -719,6 +719,8 @@ def build_IR(self, expr, args, kwargs, context): class MethodID(FoldedFunction): _id = "method_id" + _inputs = [("value", StringT.any())] + _kwargs = {"output_type": KwargSettings("TYPE_DEFINITION", BytesT(4))} def evaluate(self, node): validate_call_args(node, 1, ["output_type"]) @@ -729,7 +731,7 @@ def evaluate(self, node): if " " in value.value: raise InvalidLiteral("Invalid function signature - no spaces allowed.") - return_type = self.infer_kwarg_types(node) + return_type = self.infer_kwarg_types(node)["output_type"].typedef value = method_id_int(value.value) if return_type.compare_type(BYTES4_T): @@ -740,21 +742,20 @@ def evaluate(self, node): def fetch_call_return(self, node): validate_call_args(node, 1, ["output_type"]) - type_ = self.infer_kwarg_types(node) + type_ = self.infer_kwarg_types(node)["output_type"].typedef return type_ def infer_kwarg_types(self, node): + # If `output_type` is not given, default to `Bytes[4]` + output_typedef = TYPE_T(BytesT(4)) if node.keywords: return_type = type_from_annotation(node.keywords[0].value) if return_type.compare_type(BYTES4_T): - return BYTES4_T - elif isinstance(return_type, BytesT) and return_type.length == 4: - return BytesT(4) - else: + output_typedef = TYPE_T(BYTES4_T) + elif not (isinstance(return_type, BytesT) and return_type.length == 4): raise ArgumentException("output_type must be Bytes[4] or bytes4", node.keywords[0]) - # If `output_type` is not given, default to `Bytes[4]` - return BytesT(4) + return {"output_type": output_typedef} class ECRecover(BuiltinFunction): From c7585c6fd46a5dfc444578a3f0d62a9ae99ed0d1 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 16 Nov 2023 21:54:27 +0800 Subject: [PATCH 073/120] clean up NameConstant --- vyper/ast/nodes.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index 578a5952c7..59d3ed55a1 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -908,10 +908,6 @@ class Dict(ExprNode): class NameConstant(Constant): __slots__ = ("value",) - def __init__(self, parent: Optional["VyperNode"] = None, **kwargs: dict): - super().__init__(parent, **kwargs) - self._metadata["folded_value"] = self - class Name(ExprNode): __slots__ = ("id",) From 12ac785bffce44e01c1b954837df5388bb008b47 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 16 Nov 2023 21:59:58 +0800 Subject: [PATCH 074/120] rewrite shift check --- vyper/builtins/functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 6b09c479a1..b4456a2849 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -1468,7 +1468,7 @@ def evaluate(self, node): validate_call_args(node, 2) args = [i._metadata.get("folded_value") for i in node.args] - if [i for i in args if not isinstance(i, vy_ast.Int)]: + if any(not isinstance(i, vy_ast.Int) for i in args): raise UnfoldableNode value, shift = [i.value for i in args] if value < 0 or value >= 2**256: From 37681b776f74d3deb594a05673c8354c75bddac3 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 17 Nov 2023 15:43:23 +0800 Subject: [PATCH 075/120] clean up builtins --- vyper/builtins/functions.py | 62 ++++++------------------------------- 1 file changed, 10 insertions(+), 52 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index b4456a2849..0fbef45894 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -1,7 +1,6 @@ import hashlib import math import operator -from decimal import Decimal from vyper import ast as vy_ast from vyper.abi_types import ABI_Tuple @@ -44,7 +43,6 @@ CompilerPanic, InvalidLiteral, InvalidType, - OverflowException, StateAccessViolation, StructureException, TypeMismatch, @@ -88,7 +86,6 @@ EIP_170_LIMIT, SHA3_PER_WORD, MemoryPositions, - SizeLimits, bytes_to_int, ceil32, fourbytes_to_int, @@ -729,7 +726,7 @@ def evaluate(self, node): if not isinstance(value, vy_ast.Str): raise InvalidType("method id must be given as a literal string", node.args[0]) if " " in value.value: - raise InvalidLiteral("Invalid function signature - no spaces allowed.") + raise InvalidLiteral("Invalid function signature - no spaces allowed.", node.args[0]) return_type = self.infer_kwarg_types(node)["output_type"].typedef value = method_id_int(value.value) @@ -1005,11 +1002,6 @@ def evaluate(self, node): if value < 0: raise InvalidLiteral("Negative wei value not allowed", node.args[0]) - if isinstance(value, int) and value >= 2**256: - raise InvalidLiteral("Value out of range for uint256", node.args[0]) - if isinstance(value, Decimal) and value > SizeLimits.MAX_AST_DECIMAL: - raise InvalidLiteral("Value out of range for decimal", node.args[0]) - return vy_ast.Int.from_node(node, value=int(value * denom)) def fetch_call_return(self, node): @@ -1359,11 +1351,9 @@ def evaluate(self, node): validate_call_args(node, 2) values = [i._metadata.get("folded_value") for i in node.args] - for val, arg in zip(values, node.args): + for val in values: if not isinstance(val, vy_ast.Int): raise UnfoldableNode - if val.value < 0 or val.value >= 2**256: - raise InvalidLiteral("Value out of range for uint256", arg) value = values[0].value & values[1].value return vy_ast.Int.from_node(node, value=value) @@ -1386,11 +1376,9 @@ def evaluate(self, node): validate_call_args(node, 2) values = [i._metadata.get("folded_value") for i in node.args] - for val, arg in zip(values, node.args): + for val in values: if not isinstance(val, vy_ast.Int): raise UnfoldableNode - if val.value < 0 or val.value >= 2**256: - raise InvalidLiteral("Value out of range for uint256", arg) value = values[0].value | values[1].value return vy_ast.Int.from_node(node, value=value) @@ -1413,11 +1401,9 @@ def evaluate(self, node): validate_call_args(node, 2) values = [i._metadata.get("folded_value") for i in node.args] - for val, arg in zip(values, node.args): + for val in values: if not isinstance(val, vy_ast.Int): raise UnfoldableNode - if val.value < 0 or val.value >= 2**256: - raise InvalidLiteral("Value out of range for uint256", arg) value = values[0].value ^ values[1].value return vy_ast.Int.from_node(node, value=value) @@ -1444,8 +1430,6 @@ def evaluate(self, node): raise UnfoldableNode value = value.value - if value < 0 or value >= 2**256: - raise InvalidLiteral("Value out of range for uint256", node.args[0]) value = (2**256 - 1) - value return vy_ast.Int.from_node(node, value=value) @@ -1471,8 +1455,6 @@ def evaluate(self, node): if any(not isinstance(i, vy_ast.Int) for i in args): raise UnfoldableNode value, shift = [i.value for i in args] - if value < 0 or value >= 2**256: - raise InvalidLiteral("Value out of range for uint256", node.args[0]) if shift < -256 or shift > 256: # this validation is performed to prevent the compiler from hanging # rather than for correctness because the post-folded constant would @@ -1519,11 +1501,9 @@ def evaluate(self, node): args = [i._metadata.get("folded_value") for i in node.args] if isinstance(args[2], vy_ast.Int) and args[2].value == 0: raise ZeroDivisionException("Modulo by 0", node.args[2]) - for arg, prefolded in zip(node.args, args): - if not isinstance(prefolded, vy_ast.Int): + for arg in args: + if not isinstance(arg, vy_ast.Int): raise UnfoldableNode - if prefolded.value < 0 or prefolded.value >= 2**256: - raise InvalidLiteral("Value out of range for uint256", arg) value = self._eval_fn(args[0].value, args[1].value) % args[2].value return vy_ast.Int.from_node(node, value=value) @@ -1564,9 +1544,6 @@ def evaluate(self, node): raise UnfoldableNode left, right = values - if left.value < 0 or right.value < 0: - raise UnfoldableNode - value = pow(left.value, right.value, 2**256) return vy_ast.Int.from_node(node, value=value) @@ -1587,13 +1564,7 @@ def evaluate(self, node): if not isinstance(value, vy_ast.Int): raise UnfoldableNode - value = value.value - if not SizeLimits.MIN_INT256 <= value <= SizeLimits.MAX_INT256: - raise OverflowException("Literal is outside of allowable range for int256") - value = abs(value) - if not SizeLimits.MIN_INT256 <= value <= SizeLimits.MAX_INT256: - raise OverflowException("Absolute literal value is outside allowable range for int256") - + value = abs(value.value) return vy_ast.Int.from_node(node, value=value) def build_IR(self, expr, context): @@ -2036,12 +2007,6 @@ def evaluate(self, node): if not isinstance(left, (vy_ast.Decimal, vy_ast.Int)): raise UnfoldableNode - if isinstance(left.value, Decimal) and ( - min(left.value, right.value) < SizeLimits.MIN_AST_DECIMAL - or max(left.value, right.value) > SizeLimits.MAX_AST_DECIMAL - ): - raise InvalidType("Decimal value is outside of allowable range", node) - types_list = get_common_types( *(left, right), filter_fn=lambda x: isinstance(x, (IntegerT, DecimalT)) ) @@ -2064,16 +2029,9 @@ def fetch_call_return(self, node): def infer_arg_types(self, node, expected_return_typ=None): types_list = self.fetch_call_return(node) - - if expected_return_typ is not None: - if expected_return_typ not in types_list: - raise TypeMismatch("Cannot perform action between dislike numeric types", node) - - arg_typ = expected_return_typ - else: - arg_typ = types_list.pop() - - return [arg_typ, arg_typ] + # type mismatch should have been caught in `fetch_call_return` + assert expected_return_typ in types_list + return [expected_return_typ, expected_return_typ] @process_inputs def build_IR(self, expr, args, kwargs, context): From d82dbc044168d20c3c1d27ad13bfab24254418f2 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 17 Nov 2023 15:43:30 +0800 Subject: [PATCH 076/120] add builtins tests --- tests/functional/syntax/test_abs.py | 22 +++++++++++++++++++- tests/functional/syntax/test_as_wei_value.py | 13 +++++++++++- tests/functional/syntax/test_method_id.py | 20 +++++++++++++++++- tests/functional/syntax/test_minmax.py | 10 ++++++++- tests/functional/syntax/test_powmod.py | 18 ++++++++++++++++ 5 files changed, 79 insertions(+), 4 deletions(-) diff --git a/tests/functional/syntax/test_abs.py b/tests/functional/syntax/test_abs.py index ee207c2d5a..cb231a33b1 100644 --- a/tests/functional/syntax/test_abs.py +++ b/tests/functional/syntax/test_abs.py @@ -1,6 +1,26 @@ import pytest from vyper import compiler +from vyper.exceptions import InvalidType + +fail_list = [ + ( + """ +@external +def foo(): + y: int256 = abs( + -57896044618658097711785492504343953926634992332820282019728792003956564819968 + ) + """, + InvalidType, + ) +] + + +@pytest.mark.parametrize("bad_code,exc", fail_list) +def test_abs_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_code, exc): + assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code), exc) + valid_list = [ """ @@ -11,5 +31,5 @@ @pytest.mark.parametrize("code", valid_list) -def test_addmulmod_pass(code): +def test_abs_pass(code): assert compiler.compile_code(code) is not None diff --git a/tests/functional/syntax/test_as_wei_value.py b/tests/functional/syntax/test_as_wei_value.py index 04e4ee6b26..cbae262318 100644 --- a/tests/functional/syntax/test_as_wei_value.py +++ b/tests/functional/syntax/test_as_wei_value.py @@ -1,6 +1,6 @@ import pytest -from vyper.exceptions import ArgumentException, InvalidType, StructureException +from vyper.exceptions import ArgumentException, InvalidType, OverflowException, StructureException fail_list = [ ( @@ -28,6 +28,17 @@ def foo(): """, InvalidType, ), + ( + """ +@external +def foo() -> uint256: + return as_wei_value( + 115792089237316195423570985008687907853269984665640564039457584007913129639937, + 'milliether' + ) + """, + OverflowException, + ), ] diff --git a/tests/functional/syntax/test_method_id.py b/tests/functional/syntax/test_method_id.py index dc7b44dff9..1a59f6bd6e 100644 --- a/tests/functional/syntax/test_method_id.py +++ b/tests/functional/syntax/test_method_id.py @@ -1,6 +1,24 @@ import pytest from vyper import compiler +from vyper.exceptions import InvalidLiteral + +fail_list = [ + ( + """ +@external +def foo(): + a: Bytes[4] = method_id("bar ()") + """, + InvalidLiteral, + ) +] + + +@pytest.mark.parametrize("bad_code,exc", fail_list) +def test_method_id_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_code, exc): + assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code), exc) + valid_list = [ """ @@ -15,5 +33,5 @@ def foo(a: Bytes[4] = BAR): @pytest.mark.parametrize("code", valid_list) -def test_addmulmod_pass(code): +def test_method_id_pass(code): assert compiler.compile_code(code) is not None diff --git a/tests/functional/syntax/test_minmax.py b/tests/functional/syntax/test_minmax.py index c7c1085811..6565974f19 100644 --- a/tests/functional/syntax/test_minmax.py +++ b/tests/functional/syntax/test_minmax.py @@ -1,7 +1,7 @@ import pytest from vyper import compiler -from vyper.exceptions import InvalidType, TypeMismatch +from vyper.exceptions import InvalidType, OverflowException, TypeMismatch fail_list = [ ( @@ -20,6 +20,14 @@ def foo(): """, TypeMismatch, ), + ( + """ +@external +def foo(): + a: decimal = min(1.0, 18707220957835557353007165858768422651595.9365500928) + """, + OverflowException, + ), ] diff --git a/tests/functional/syntax/test_powmod.py b/tests/functional/syntax/test_powmod.py index 032f8b841f..fce692a74a 100644 --- a/tests/functional/syntax/test_powmod.py +++ b/tests/functional/syntax/test_powmod.py @@ -1,6 +1,24 @@ import pytest from vyper import compiler +from vyper.exceptions import InvalidType + +fail_list = [ + ( + """ +@external +def foo(): + a: uint256 = pow_mod256(-1, -1) + """, + InvalidType, + ) +] + + +@pytest.mark.parametrize("bad_code,exc", fail_list) +def test_powmod_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_code, exc): + assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code), exc) + valid_list = [ """ From eda9ba814ad5eb25d3a656fb880670ddb1246de4 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 17 Nov 2023 15:53:43 +0800 Subject: [PATCH 077/120] fix call args constancy --- vyper/semantics/analysis/local.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 342d1aa243..26b767f086 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -189,15 +189,14 @@ def __init__( self.expr_visitor = ExprVisitor(self.func) # allow internal function params to be mutable - location, is_immutable = ( - (DataLocation.MEMORY, False) if self.func.is_internal else (DataLocation.CALLDATA, True) + location, is_immutable, constancy = ( + (DataLocation.MEMORY, False, VariableConstancy.MUTABLE) + if self.func.is_internal + else (DataLocation.CALLDATA, True, VariableConstancy.RUNTIME_CONSTANT) ) for arg in self.func.arguments: namespace[arg.name] = VarInfo( - arg.typ, - location=location, - is_immutable=is_immutable, - constancy=VariableConstancy.RUNTIME_CONSTANT, + arg.typ, location=location, is_immutable=is_immutable, constancy=constancy ) for node in fn_node.body: From 2520b9093410f5fab0116488a3c5761bc83fcae4 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 17 Nov 2023 16:37:27 +0800 Subject: [PATCH 078/120] fix abs test --- tests/functional/builtins/folding/test_abs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/builtins/folding/test_abs.py b/tests/functional/builtins/folding/test_abs.py index f450e434a4..5929ef458b 100644 --- a/tests/functional/builtins/folding/test_abs.py +++ b/tests/functional/builtins/folding/test_abs.py @@ -4,7 +4,7 @@ from vyper import ast as vy_ast from vyper.builtins import functions as vy_fn -from vyper.exceptions import InvalidType, OverflowException +from vyper.exceptions import InvalidType @pytest.mark.fuzzing @@ -56,5 +56,5 @@ def test_abs_lower_bound_folded(get_contract, assert_tx_failed): def foo() -> int256: return abs(min_value(int256)) """ - with pytest.raises(OverflowException): + with pytest.raises(InvalidType): get_contract(source) From 3d7d317b810723729446586dbf1cadc24e5d03d8 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 17 Nov 2023 19:07:48 +0800 Subject: [PATCH 079/120] improve error msg for as_wei_value --- tests/functional/syntax/test_as_wei_value.py | 24 +++++++++++++++++++- vyper/builtins/functions.py | 9 +++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/tests/functional/syntax/test_as_wei_value.py b/tests/functional/syntax/test_as_wei_value.py index cbae262318..fd5f9e20ba 100644 --- a/tests/functional/syntax/test_as_wei_value.py +++ b/tests/functional/syntax/test_as_wei_value.py @@ -1,11 +1,25 @@ import pytest -from vyper.exceptions import ArgumentException, InvalidType, OverflowException, StructureException +from vyper.exceptions import ( + ArgumentException, + InvalidLiteral, + InvalidType, + OverflowException, + StructureException, +) fail_list = [ ( """ @external +def foo(): + x: uint256 = as_wei_value(5, szabo) + """, + ArgumentException, + ), + ( + """ +@external def foo(): x: uint256 = as_wei_value(5, "szaboo") """, @@ -39,6 +53,14 @@ def foo() -> uint256: """, OverflowException, ), + ( + """ +@external +def foo(): + x: uint256 = as_wei_value(-1, "szabo") + """, + InvalidLiteral, + ), ] diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 0fbef45894..2aea650ea0 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -977,12 +977,16 @@ class AsWeiValue(BuiltinFunction): ("kether", "grand"): 10**21, } - def get_denomination(self, node): + def _get_denomination_node(self, node): value = node.args[1]._metadata.get("folded_value") if not isinstance(value, vy_ast.Str): raise ArgumentException( "Wei denomination must be given as a literal string", node.args[1] ) + return value + + def get_denomination(self, node): + value = self._get_denomination_node(node) try: denom = next(v for k, v in self.wei_denoms.items() if value.value in k) except StopIteration: @@ -1009,6 +1013,9 @@ def fetch_call_return(self, node): return self._return_type def infer_arg_types(self, node, expected_return_typ=None): + # raise a better error message by first calling this function + # for its side effects of checking the denom + self._get_denomination_node(node) self._validate_arg_types(node) # return a concrete type instead of abstract type value_type = get_possible_types_from_node(node.args[0]).pop() From c77244c5a06d75a1f63fd4b528e90ff2aeaca366 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 17 Nov 2023 22:32:47 +0800 Subject: [PATCH 080/120] fix folded builtins for constants --- tests/functional/syntax/test_as_wei_value.py | 18 ++++++++++++++ tests/functional/syntax/test_epsilon.py | 14 +++++++++++ tests/functional/syntax/test_method_id.py | 16 ++++++++++-- tests/functional/syntax/test_minmax_value.py | 3 +++ vyper/builtins/functions.py | 26 ++++++++++++++------ vyper/semantics/analysis/module.py | 2 +- 6 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 tests/functional/syntax/test_epsilon.py diff --git a/tests/functional/syntax/test_as_wei_value.py b/tests/functional/syntax/test_as_wei_value.py index fd5f9e20ba..4ed3f0a224 100644 --- a/tests/functional/syntax/test_as_wei_value.py +++ b/tests/functional/syntax/test_as_wei_value.py @@ -61,6 +61,24 @@ def foo(): """, InvalidLiteral, ), + ( + """ +FOO: constant(uint256) = as_wei_value(5, szabo) + """, + ArgumentException, + ), + ( + """ +FOO: constant(uint256) = as_wei_value(5, "szaboo") + """, + ArgumentException, + ), + ( + """ +FOO: constant(uint256) = as_wei_value(-1, "szabo") + """, + InvalidLiteral, + ), ] diff --git a/tests/functional/syntax/test_epsilon.py b/tests/functional/syntax/test_epsilon.py new file mode 100644 index 0000000000..73369d4b8a --- /dev/null +++ b/tests/functional/syntax/test_epsilon.py @@ -0,0 +1,14 @@ +import pytest + +from vyper.exceptions import InvalidType + +fail_list = [ + """ +FOO: constant(address) = epsilon(address) + """ +] + + +@pytest.mark.parametrize("bad_code", fail_list) +def test_block_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_code): + assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code), InvalidType) diff --git a/tests/functional/syntax/test_method_id.py b/tests/functional/syntax/test_method_id.py index 1a59f6bd6e..da67e67fe3 100644 --- a/tests/functional/syntax/test_method_id.py +++ b/tests/functional/syntax/test_method_id.py @@ -1,7 +1,7 @@ import pytest from vyper import compiler -from vyper.exceptions import InvalidLiteral +from vyper.exceptions import InvalidLiteral, InvalidType fail_list = [ ( @@ -11,7 +11,19 @@ def foo(): a: Bytes[4] = method_id("bar ()") """, InvalidLiteral, - ) + ), + ( + """ +FOO: constant(Bytes[4]) = method_id(1) + """, + InvalidType, + ), + ( + """ +FOO: constant(Bytes[4]) = method_id("bar ()") + """, + InvalidLiteral, + ), ] diff --git a/tests/functional/syntax/test_minmax_value.py b/tests/functional/syntax/test_minmax_value.py index e154cad23f..14b61e2f0a 100644 --- a/tests/functional/syntax/test_minmax_value.py +++ b/tests/functional/syntax/test_minmax_value.py @@ -13,6 +13,9 @@ def foo(): def foo(): a: address = max_value(address) """, + """ +FOO: constant(address) = min_value(address) + """, ] diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 2aea650ea0..9106c0def1 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -742,6 +742,11 @@ def fetch_call_return(self, node): type_ = self.infer_kwarg_types(node)["output_type"].typedef return type_ + def infer_arg_types(self, node, expected_return_typ=None): + # call `evaluate` for its typechecking side effects + self.evaluate(node) + return [self._inputs[0][1]] + def infer_kwarg_types(self, node): # If `output_type` is not given, default to `Bytes[4]` output_typedef = TYPE_T(BytesT(4)) @@ -977,16 +982,12 @@ class AsWeiValue(BuiltinFunction): ("kether", "grand"): 10**21, } - def _get_denomination_node(self, node): + def get_denomination(self, node): value = node.args[1]._metadata.get("folded_value") if not isinstance(value, vy_ast.Str): raise ArgumentException( "Wei denomination must be given as a literal string", node.args[1] ) - return value - - def get_denomination(self, node): - value = self._get_denomination_node(node) try: denom = next(v for k, v in self.wei_denoms.items() if value.value in k) except StopIteration: @@ -1013,9 +1014,12 @@ def fetch_call_return(self, node): return self._return_type def infer_arg_types(self, node, expected_return_typ=None): - # raise a better error message by first calling this function - # for its side effects of checking the denom - self._get_denomination_node(node) + # call `evaluate` for its typechecking side effects` + try: + self.evaluate(node) + except UnfoldableNode: + pass + self._validate_arg_types(node) # return a concrete type instead of abstract type value_type = get_possible_types_from_node(node.args[0]).pop() @@ -2585,6 +2589,12 @@ def evaluate(self, node): ret._metadata["type"] = input_type return ret + def infer_arg_types(self, node, expected_return_typ=None): + # call `evaluate` for its typechecking side effects + self.evaluate(node) + input_typedef = TYPE_T(type_from_annotation(node.args[0])) + return [input_typedef] + class MinValue(_MinMaxValue): _id = "min_value" diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index 6952bb1ee4..651c92c4aa 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -240,13 +240,13 @@ def _validate_self_namespace(): raise exc.with_annotation(node) from None if node.is_constant: + ExprVisitor().visit(node.value, type_) if not node.value: raise VariableDeclarationException("Constant must be declared with a value", node) if not check_variable_constancy(node.value, VariableConstancy.COMPILE_TIME_CONSTANT): raise StateAccessViolation("Value must be a literal", node.value) validate_expected_type(node.value, type_) - ExprVisitor().visit(node.value, type_) _validate_self_namespace() return _finalize() From f39effaccc52d516243b84b683b31220192d4970 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 17 Nov 2023 23:06:47 +0800 Subject: [PATCH 081/120] fix constant var decl expr visit --- vyper/semantics/analysis/module.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index 651c92c4aa..02965eecd7 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -240,9 +240,11 @@ def _validate_self_namespace(): raise exc.with_annotation(node) from None if node.is_constant: - ExprVisitor().visit(node.value, type_) if not node.value: raise VariableDeclarationException("Constant must be declared with a value", node) + + ExprVisitor().visit(node.value, type_) + if not check_variable_constancy(node.value, VariableConstancy.COMPILE_TIME_CONSTANT): raise StateAccessViolation("Value must be a literal", node.value) From e55eba665773190933a9c80ac3e5f1598b3d4f4c Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 17 Nov 2023 23:09:31 +0800 Subject: [PATCH 082/120] fix lint --- vyper/semantics/analysis/module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index 02965eecd7..e57f72465b 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -242,9 +242,9 @@ def _validate_self_namespace(): if node.is_constant: if not node.value: raise VariableDeclarationException("Constant must be declared with a value", node) - + ExprVisitor().visit(node.value, type_) - + if not check_variable_constancy(node.value, VariableConstancy.COMPILE_TIME_CONSTANT): raise StateAccessViolation("Value must be a literal", node.value) From 5816107b86af00fec61187237a9f0463ecc92847 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sat, 18 Nov 2023 15:38:34 +0800 Subject: [PATCH 083/120] add unaryop and compare to constancy check --- .../test_default_parameters.py | 20 +++++++++++++++++++ vyper/semantics/analysis/utils.py | 5 ++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/functional/codegen/calling_convention/test_default_parameters.py b/tests/functional/codegen/calling_convention/test_default_parameters.py index 7a5673c281..3f5c717fd2 100644 --- a/tests/functional/codegen/calling_convention/test_default_parameters.py +++ b/tests/functional/codegen/calling_convention/test_default_parameters.py @@ -326,6 +326,26 @@ def out_literals(a: int128 = BAR.x + 1) -> X: def out_literals(a: int128 = FOO.x.x + 1) -> Y: return FOO """, + """ +struct Bar: + a: bool + +BAR: constant(Bar) = Bar({a: True}) + +@external +def foo(x: bool = True and not BAR.a): + pass + """, + """ +struct Bar: + a: uint256 + +BAR: constant(Bar) = Bar({ a: 123 }) + +@external +def foo(x: bool = BAR.a + 1 > 456): + pass + """, ] diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 3baa6b4464..3994bd810c 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -637,12 +637,15 @@ def check_variable_constancy(node: vy_ast.VyperNode, constancy: VariableConstanc if _check_literal(node): return True - if isinstance(node, vy_ast.BinOp): + if isinstance(node, (vy_ast.BinOp, vy_ast.Compare)): return all(check_variable_constancy(i, constancy) for i in (node.left, node.right)) if isinstance(node, vy_ast.BoolOp): return all(check_variable_constancy(i, constancy) for i in node.values) + if isinstance(node, vy_ast.UnaryOp): + return check_variable_constancy(node.operand, constancy) + if isinstance(node, (vy_ast.Tuple, vy_ast.List)): return all(check_variable_constancy(item, constancy) for item in node.elements) From 562030129dd1d28bd3af5749204bac50cc96c97c Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sat, 18 Nov 2023 15:43:30 +0800 Subject: [PATCH 084/120] add for bound tests --- tests/functional/syntax/test_for_range.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/functional/syntax/test_for_range.py b/tests/functional/syntax/test_for_range.py index 1fbe2c95f1..fe5b378501 100644 --- a/tests/functional/syntax/test_for_range.py +++ b/tests/functional/syntax/test_for_range.py @@ -82,6 +82,17 @@ def foo(): """, TypeMismatch, ), + ( + """ +FOO: constant(int128) = -1 + +@external +def foo(): + for i in range(10, bound=FOO): + pass + """, + StructureException, + ) ] From 6a5e00d11ffe3340fc9ffd6cbdc199e811d7f0bf Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sat, 18 Nov 2023 15:43:39 +0800 Subject: [PATCH 085/120] clean up index value getter --- vyper/semantics/types/utils.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/vyper/semantics/types/utils.py b/vyper/semantics/types/utils.py index 4a1dd98024..9b66ef5c22 100644 --- a/vyper/semantics/types/utils.py +++ b/vyper/semantics/types/utils.py @@ -151,11 +151,7 @@ def get_index_value(node: vy_ast.Index) -> int: # TODO: revisit this! from vyper.semantics.analysis.utils import get_possible_types_from_node - value = ( - node.value - if isinstance(node.value, vy_ast.Int) - else node.value._metadata.get("folded_value") - ) + value = node.value._metadata.get("folded_value") if not isinstance(value, vy_ast.Int): if hasattr(node, "value"): # even though the subscript is an invalid type, first check if it's a valid _something_ From 6505a94a221934f7d0a4e84311ada476508208db Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sat, 18 Nov 2023 15:45:07 +0800 Subject: [PATCH 086/120] fix lint --- tests/functional/syntax/test_for_range.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/syntax/test_for_range.py b/tests/functional/syntax/test_for_range.py index fe5b378501..3941d8ec44 100644 --- a/tests/functional/syntax/test_for_range.py +++ b/tests/functional/syntax/test_for_range.py @@ -92,7 +92,7 @@ def foo(): pass """, StructureException, - ) + ), ] From 0f2302baedd396a2f398d7bc462fd14866e3d90d Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 21 Dec 2023 17:28:34 +0800 Subject: [PATCH 087/120] fix lint --- vyper/compiler/phases.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index d0014278d3..874820f745 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -282,9 +282,6 @@ def generate_folded_ast( vyper_module_folded = copy.deepcopy(vyper_module) vy_ast.folding.fold(vyper_module_folded) - # with input_bundle.search_path(Path(vyper_module.resolved_path).parent): - # validate_semantics(vyper_module_folded, input_bundle) - return vyper_module_folded, symbol_tables From 4256dcaf22595537bf3e0ed4e9edd92d7a071749 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 21 Dec 2023 17:52:09 +0800 Subject: [PATCH 088/120] fix tests; relax type propagation in folding builtins --- tests/unit/ast/test_folding.py | 68 +++++++++++++++++----------------- vyper/ast/folding.py | 3 +- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/tests/unit/ast/test_folding.py b/tests/unit/ast/test_folding.py index 6564da1c3b..8347fa90dd 100644 --- a/tests/unit/ast/test_folding.py +++ b/tests/unit/ast/test_folding.py @@ -6,7 +6,7 @@ from vyper.semantics import validate_semantics -def test_integration(): +def test_integration(dummy_input_bundle): test = """ @external def foo(): @@ -22,13 +22,13 @@ def foo(): test_ast = vy_ast.parse_to_ast(test) expected_ast = vy_ast.parse_to_ast(expected) - validate_semantics(test_ast, {}) + validate_semantics(test_ast, dummy_input_bundle) folding.fold(test_ast) assert vy_ast.compare_nodes(test_ast, expected_ast) -def test_replace_binop_simple(): +def test_replace_binop_simple(dummy_input_bundle): test = """ @external def foo(): @@ -44,13 +44,13 @@ def foo(): test_ast = vy_ast.parse_to_ast(test) expected_ast = vy_ast.parse_to_ast(expected) - validate_semantics(test_ast, {}) + validate_semantics(test_ast, dummy_input_bundle) folding.replace_literal_ops(test_ast) assert vy_ast.compare_nodes(test_ast, expected_ast) -def test_replace_binop_nested(): +def test_replace_binop_nested(dummy_input_bundle): test = """ @external def foo(): @@ -65,13 +65,13 @@ def foo(): test_ast = vy_ast.parse_to_ast(test) expected_ast = vy_ast.parse_to_ast(expected) - validate_semantics(test_ast, {}) + validate_semantics(test_ast, dummy_input_bundle) folding.replace_literal_ops(test_ast) assert vy_ast.compare_nodes(test_ast, expected_ast) -def test_replace_binop_nested_intermediate_overflow(): +def test_replace_binop_nested_intermediate_overflow(dummy_input_bundle): test = """ @external def foo(): @@ -79,10 +79,10 @@ def foo(): """ test_ast = vy_ast.parse_to_ast(test) with pytest.raises(OverflowException): - validate_semantics(test_ast, {}) + validate_semantics(test_ast, dummy_input_bundle) -def test_replace_binop_nested_intermediate_underflow(): +def test_replace_binop_nested_intermediate_underflow(dummy_input_bundle): test = """ @external def foo(): @@ -90,10 +90,10 @@ def foo(): """ test_ast = vy_ast.parse_to_ast(test) with pytest.raises(InvalidType): - validate_semantics(test_ast, {}) + validate_semantics(test_ast, dummy_input_bundle) -def test_replace_decimal_nested_intermediate_overflow(): +def test_replace_decimal_nested_intermediate_overflow(dummy_input_bundle): test = """ @external def foo(): @@ -101,10 +101,10 @@ def foo(): """ test_ast = vy_ast.parse_to_ast(test) with pytest.raises(OverflowException): - validate_semantics(test_ast, {}) + validate_semantics(test_ast, dummy_input_bundle) -def test_replace_decimal_nested_intermediate_underflow(): +def test_replace_decimal_nested_intermediate_underflow(dummy_input_bundle): test = """ @external def foo(): @@ -112,10 +112,10 @@ def foo(): """ test_ast = vy_ast.parse_to_ast(test) with pytest.raises(OverflowException): - validate_semantics(test_ast, {}) + validate_semantics(test_ast, dummy_input_bundle) -def test_replace_literal_ops(): +def test_replace_literal_ops(dummy_input_bundle): test = """ @external def foo(): @@ -130,13 +130,13 @@ def foo(): test_ast = vy_ast.parse_to_ast(test) expected_ast = vy_ast.parse_to_ast(expected) - validate_semantics(test_ast, {}) + validate_semantics(test_ast, dummy_input_bundle) folding.replace_literal_ops(test_ast) assert vy_ast.compare_nodes(test_ast, expected_ast) -def test_replace_subscripts_simple(): +def test_replace_subscripts_simple(dummy_input_bundle): test = """ @external def foo(): @@ -151,13 +151,13 @@ def foo(): test_ast = vy_ast.parse_to_ast(test) expected_ast = vy_ast.parse_to_ast(expected) - validate_semantics(test_ast, {}) + validate_semantics(test_ast, dummy_input_bundle) folding.replace_subscripts(test_ast) assert vy_ast.compare_nodes(test_ast, expected_ast) -def test_replace_subscripts_nested(): +def test_replace_subscripts_nested(dummy_input_bundle): test = """ @external def foo(): @@ -172,7 +172,7 @@ def foo(): test_ast = vy_ast.parse_to_ast(test) expected_ast = vy_ast.parse_to_ast(expected) - validate_semantics(test_ast, {}) + validate_semantics(test_ast, dummy_input_bundle) folding.replace_subscripts(test_ast) assert vy_ast.compare_nodes(test_ast, expected_ast) @@ -240,11 +240,11 @@ def bar(x: DynArray[uint256, 4]): @pytest.mark.parametrize("source", constants_modified) -def test_replace_constant(source): +def test_replace_constant(dummy_input_bundle, source): unmodified_ast = vy_ast.parse_to_ast(source) folded_ast = vy_ast.parse_to_ast(source) - validate_semantics(folded_ast, {}) + validate_semantics(folded_ast, dummy_input_bundle) folding.replace_user_defined_constants(folded_ast) assert not vy_ast.compare_nodes(unmodified_ast, folded_ast) @@ -321,11 +321,11 @@ def foo(): @pytest.mark.parametrize("source", constants_unmodified) -def test_replace_constant_no(source): +def test_replace_constant_no(dummy_input_bundle, source): unmodified_ast = vy_ast.parse_to_ast(source) folded_ast = vy_ast.parse_to_ast(source) - validate_semantics(folded_ast, {}) + validate_semantics(folded_ast, dummy_input_bundle) folding.replace_user_defined_constants(folded_ast) assert vy_ast.compare_nodes(unmodified_ast, folded_ast) @@ -367,13 +367,13 @@ def foo() -> int128: @pytest.mark.parametrize("source", userdefined_modified) -def test_replace_userdefined_constant(source): +def test_replace_userdefined_constant(dummy_input_bundle, source): source = f"FOO: constant(int128) = 42\n{source}" unmodified_ast = vy_ast.parse_to_ast(source) folded_ast = vy_ast.parse_to_ast(source) - validate_semantics(folded_ast, {}) + validate_semantics(folded_ast, dummy_input_bundle) folding.replace_user_defined_constants(folded_ast) assert not vy_ast.compare_nodes(unmodified_ast, folded_ast) @@ -397,13 +397,13 @@ def foo(): @pytest.mark.parametrize("source", userdefined_attributes) -def test_replace_userdefined_attribute(source): +def test_replace_userdefined_attribute(dummy_input_bundle, source): preamble = f"ADDR: constant(address) = {dummy_address}" l_source = f"{preamble}\n{source[0]}" r_source = f"{preamble}\n{source[1]}" l_ast = vy_ast.parse_to_ast(l_source) - validate_semantics(l_ast, {}) + validate_semantics(l_ast, dummy_input_bundle) folding.replace_user_defined_constants(l_ast) r_ast = vy_ast.parse_to_ast(r_source) @@ -428,7 +428,7 @@ def foo(): @pytest.mark.parametrize("source", userdefined_struct) -def test_replace_userdefined_struct(source): +def test_replace_userdefined_struct(dummy_input_bundle, source): preamble = """ struct Foo: a: uint256 @@ -440,7 +440,7 @@ def test_replace_userdefined_struct(source): r_source = f"{preamble}\n{source[1]}" l_ast = vy_ast.parse_to_ast(l_source) - validate_semantics(l_ast, {}) + validate_semantics(l_ast, dummy_input_bundle) folding.replace_user_defined_constants(l_ast) r_ast = vy_ast.parse_to_ast(r_source) @@ -465,7 +465,7 @@ def foo(): @pytest.mark.parametrize("source", userdefined_nested_struct) -def test_replace_userdefined_nested_struct(source): +def test_replace_userdefined_nested_struct(dummy_input_bundle, source): preamble = """ struct Bar: b1: uint256 @@ -481,7 +481,7 @@ def test_replace_userdefined_nested_struct(source): r_source = f"{preamble}\n{source[1]}" l_ast = vy_ast.parse_to_ast(l_source) - validate_semantics(l_ast, {}) + validate_semantics(l_ast, dummy_input_bundle) folding.replace_user_defined_constants(l_ast) r_ast = vy_ast.parse_to_ast(r_source) @@ -515,11 +515,11 @@ def foo(bar: int256 = {}): @pytest.mark.parametrize("source", builtin_folding_sources) @pytest.mark.parametrize("original,result", builtin_folding_functions) -def test_replace_builtins(source, original, result): +def test_replace_builtins(dummy_input_bundle, source, original, result): original_ast = vy_ast.parse_to_ast(source.format(original)) target_ast = vy_ast.parse_to_ast(source.format(result)) - validate_semantics(original_ast, {}) + validate_semantics(original_ast, dummy_input_bundle) folding.replace_builtin_functions(original_ast) assert vy_ast.compare_nodes(original_ast, target_ast) diff --git a/vyper/ast/folding.py b/vyper/ast/folding.py index 0863dba174..51a58f0bb8 100644 --- a/vyper/ast/folding.py +++ b/vyper/ast/folding.py @@ -121,7 +121,8 @@ def replace_builtin_functions(vyper_module: vy_ast.Module) -> int: except UnfoldableNode: continue - new_node._metadata["type"] = node._metadata["type"] + if "type" in node._metadata: + new_node._metadata["type"] = node._metadata["type"] changed_nodes += 1 vyper_module.replace_in_tree(node, new_node) From ce3f61fb55507f9e0734cdc971cb3a2b7178f407 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 21 Dec 2023 19:14:49 +0800 Subject: [PATCH 089/120] rename evaluate to fold --- tests/functional/builtins/folding/test_abs.py | 2 +- .../builtins/folding/test_addmod_mulmod.py | 2 +- .../builtins/folding/test_bitwise.py | 8 +-- .../builtins/folding/test_epsilon.py | 2 +- .../builtins/folding/test_floor_ceil.py | 2 +- .../folding/test_fold_as_wei_value.py | 4 +- .../builtins/folding/test_keccak_sha.py | 6 +-- tests/functional/builtins/folding/test_len.py | 6 +-- .../builtins/folding/test_min_max.py | 6 +-- .../builtins/folding/test_powmod.py | 2 +- ..._decimal.py => test_fold_binop_decimal.py} | 4 +- ...te_binop_int.py => test_fold_binop_int.py} | 6 +-- ...evaluate_boolop.py => test_fold_boolop.py} | 2 +- ...aluate_compare.py => test_fold_compare.py} | 10 ++-- ...te_subscript.py => test_fold_subscript.py} | 2 +- ...aluate_unaryop.py => test_fold_unaryop.py} | 2 +- vyper/ast/README.md | 3 +- vyper/ast/folding.py | 8 +-- vyper/ast/nodes.py | 16 +++--- vyper/ast/nodes.pyi | 2 +- vyper/builtins/_signatures.py | 4 +- vyper/builtins/functions.py | 50 +++++++++---------- 22 files changed, 74 insertions(+), 75 deletions(-) rename tests/unit/ast/nodes/{test_evaluate_binop_decimal.py => test_fold_binop_decimal.py} (97%) rename tests/unit/ast/nodes/{test_evaluate_binop_int.py => test_fold_binop_int.py} (96%) rename tests/unit/ast/nodes/{test_evaluate_boolop.py => test_fold_boolop.py} (98%) rename tests/unit/ast/nodes/{test_evaluate_compare.py => test_fold_compare.py} (95%) rename tests/unit/ast/nodes/{test_evaluate_subscript.py => test_fold_subscript.py} (94%) rename tests/unit/ast/nodes/{test_evaluate_unaryop.py => test_fold_unaryop.py} (96%) diff --git a/tests/functional/builtins/folding/test_abs.py b/tests/functional/builtins/folding/test_abs.py index 5929ef458b..339b549135 100644 --- a/tests/functional/builtins/folding/test_abs.py +++ b/tests/functional/builtins/folding/test_abs.py @@ -21,7 +21,7 @@ def foo(a: int256) -> int256: vyper_ast = vy_ast.parse_to_ast(f"abs({a})") old_node = vyper_ast.body[0].value - new_node = vy_fn.DISPATCH_TABLE["abs"].evaluate(old_node) + new_node = vy_fn.DISPATCH_TABLE["abs"].fold(old_node) assert contract.foo(a) == new_node.value == abs(a) diff --git a/tests/functional/builtins/folding/test_addmod_mulmod.py b/tests/functional/builtins/folding/test_addmod_mulmod.py index 33dcc62984..03325240a5 100644 --- a/tests/functional/builtins/folding/test_addmod_mulmod.py +++ b/tests/functional/builtins/folding/test_addmod_mulmod.py @@ -24,6 +24,6 @@ def foo(a: uint256, b: uint256, c: uint256) -> uint256: vyper_ast = vy_ast.parse_to_ast(f"{fn_name}({a}, {b}, {c})") old_node = vyper_ast.body[0].value - new_node = vy_fn.DISPATCH_TABLE[fn_name].evaluate(old_node) + new_node = vy_fn.DISPATCH_TABLE[fn_name].fold(old_node) assert contract.foo(a, b, c) == new_node.value diff --git a/tests/functional/builtins/folding/test_bitwise.py b/tests/functional/builtins/folding/test_bitwise.py index 63e733644f..d5c0f1ac86 100644 --- a/tests/functional/builtins/folding/test_bitwise.py +++ b/tests/functional/builtins/folding/test_bitwise.py @@ -28,7 +28,7 @@ def foo(a: uint256, b: uint256) -> uint256: vyper_ast = vy_ast.parse_to_ast(f"{a} {op} {b}") old_node = vyper_ast.body[0].value - new_node = old_node.evaluate() + new_node = old_node.fold() assert contract.foo(a, b) == new_node.value @@ -49,7 +49,7 @@ def foo(a: uint256, b: uint256) -> uint256: old_node = vyper_ast.body[0].value try: - new_node = old_node.evaluate() + new_node = old_node.fold() # force bounds check, no-op because validate_numeric_bounds # already does this, but leave in for hygiene (in case # more types are added). @@ -79,7 +79,7 @@ def foo(a: int256, b: uint256) -> int256: old_node = vyper_ast.body[0].value try: - new_node = old_node.evaluate() + new_node = old_node.fold() validate_expected_type(new_node, INT256_T) # force bounds check # compile time behavior does not match runtime behavior. # compile-time will throw on OOB, runtime will wrap. @@ -104,6 +104,6 @@ def foo(a: uint256) -> uint256: vyper_ast = vy_ast.parse_to_ast(f"~{value}") old_node = vyper_ast.body[0].value - new_node = old_node.evaluate() + new_node = old_node.fold() assert contract.foo(value) == new_node.value diff --git a/tests/functional/builtins/folding/test_epsilon.py b/tests/functional/builtins/folding/test_epsilon.py index 794648cfce..3110d5eae5 100644 --- a/tests/functional/builtins/folding/test_epsilon.py +++ b/tests/functional/builtins/folding/test_epsilon.py @@ -15,6 +15,6 @@ def foo() -> {typ_name}: vyper_ast = vy_ast.parse_to_ast(f"epsilon({typ_name})") old_node = vyper_ast.body[0].value - new_node = vy_fn.DISPATCH_TABLE["epsilon"].evaluate(old_node) + new_node = vy_fn.DISPATCH_TABLE["epsilon"].fold(old_node) assert contract.foo() == new_node.value diff --git a/tests/functional/builtins/folding/test_floor_ceil.py b/tests/functional/builtins/folding/test_floor_ceil.py index 87db23889a..8e3f14e9ec 100644 --- a/tests/functional/builtins/folding/test_floor_ceil.py +++ b/tests/functional/builtins/folding/test_floor_ceil.py @@ -30,6 +30,6 @@ def foo(a: decimal) -> int256: vyper_ast = vy_ast.parse_to_ast(f"{fn_name}({value})") old_node = vyper_ast.body[0].value - new_node = vy_fn.DISPATCH_TABLE[fn_name].evaluate(old_node) + new_node = vy_fn.DISPATCH_TABLE[fn_name].fold(old_node) assert contract.foo(value) == new_node.value diff --git a/tests/functional/builtins/folding/test_fold_as_wei_value.py b/tests/functional/builtins/folding/test_fold_as_wei_value.py index 210ab51f0d..9af1618bcb 100644 --- a/tests/functional/builtins/folding/test_fold_as_wei_value.py +++ b/tests/functional/builtins/folding/test_fold_as_wei_value.py @@ -32,7 +32,7 @@ def foo(a: decimal) -> uint256: vyper_ast = vy_ast.parse_to_ast(f"as_wei_value({value:.10f}, '{denom}')") old_node = vyper_ast.body[0].value - new_node = vy_fn.AsWeiValue().evaluate(old_node) + new_node = vy_fn.AsWeiValue().fold(old_node) assert contract.foo(value) == new_node.value @@ -51,6 +51,6 @@ def foo(a: uint256) -> uint256: vyper_ast = vy_ast.parse_to_ast(f"as_wei_value({value}, '{denom}')") old_node = vyper_ast.body[0].value - new_node = vy_fn.AsWeiValue().evaluate(old_node) + new_node = vy_fn.AsWeiValue().fold(old_node) assert contract.foo(value) == new_node.value diff --git a/tests/functional/builtins/folding/test_keccak_sha.py b/tests/functional/builtins/folding/test_keccak_sha.py index a2fe460dd1..768a46a40d 100644 --- a/tests/functional/builtins/folding/test_keccak_sha.py +++ b/tests/functional/builtins/folding/test_keccak_sha.py @@ -22,7 +22,7 @@ def foo(a: String[100]) -> bytes32: vyper_ast = vy_ast.parse_to_ast(f"{fn_name}('''{value}''')") old_node = vyper_ast.body[0].value - new_node = vy_fn.DISPATCH_TABLE[fn_name].evaluate(old_node) + new_node = vy_fn.DISPATCH_TABLE[fn_name].fold(old_node) assert f"0x{contract.foo(value).hex()}" == new_node.value @@ -41,7 +41,7 @@ def foo(a: Bytes[100]) -> bytes32: vyper_ast = vy_ast.parse_to_ast(f"{fn_name}({value})") old_node = vyper_ast.body[0].value - new_node = vy_fn.DISPATCH_TABLE[fn_name].evaluate(old_node) + new_node = vy_fn.DISPATCH_TABLE[fn_name].fold(old_node) assert f"0x{contract.foo(value).hex()}" == new_node.value @@ -62,6 +62,6 @@ def foo(a: Bytes[100]) -> bytes32: vyper_ast = vy_ast.parse_to_ast(f"{fn_name}({value})") old_node = vyper_ast.body[0].value - new_node = vy_fn.DISPATCH_TABLE[fn_name].evaluate(old_node) + new_node = vy_fn.DISPATCH_TABLE[fn_name].fold(old_node) assert f"0x{contract.foo(value).hex()}" == new_node.value diff --git a/tests/functional/builtins/folding/test_len.py b/tests/functional/builtins/folding/test_len.py index edf33120dd..f4d54e202b 100644 --- a/tests/functional/builtins/folding/test_len.py +++ b/tests/functional/builtins/folding/test_len.py @@ -17,7 +17,7 @@ def foo(a: String[1024]) -> uint256: vyper_ast = vy_ast.parse_to_ast(f"len('{value}')") old_node = vyper_ast.body[0].value - new_node = vy_fn.Len().evaluate(old_node) + new_node = vy_fn.Len().fold(old_node) assert contract.foo(value) == new_node.value @@ -35,7 +35,7 @@ def foo(a: Bytes[1024]) -> uint256: vyper_ast = vy_ast.parse_to_ast(f"len(b'{value}')") old_node = vyper_ast.body[0].value - new_node = vy_fn.Len().evaluate(old_node) + new_node = vy_fn.Len().fold(old_node) assert contract.foo(value.encode()) == new_node.value @@ -53,6 +53,6 @@ def foo(a: Bytes[1024]) -> uint256: vyper_ast = vy_ast.parse_to_ast(f"len({value})") old_node = vyper_ast.body[0].value - new_node = vy_fn.Len().evaluate(old_node) + new_node = vy_fn.Len().fold(old_node) assert contract.foo(value) == new_node.value diff --git a/tests/functional/builtins/folding/test_min_max.py b/tests/functional/builtins/folding/test_min_max.py index 309f7519c0..4548e482ca 100644 --- a/tests/functional/builtins/folding/test_min_max.py +++ b/tests/functional/builtins/folding/test_min_max.py @@ -31,7 +31,7 @@ def foo(a: decimal, b: decimal) -> decimal: vyper_ast = vy_ast.parse_to_ast(f"{fn_name}({left}, {right})") old_node = vyper_ast.body[0].value - new_node = vy_fn.DISPATCH_TABLE[fn_name].evaluate(old_node) + new_node = vy_fn.DISPATCH_TABLE[fn_name].fold(old_node) assert contract.foo(left, right) == new_node.value @@ -50,7 +50,7 @@ def foo(a: int128, b: int128) -> int128: vyper_ast = vy_ast.parse_to_ast(f"{fn_name}({left}, {right})") old_node = vyper_ast.body[0].value - new_node = vy_fn.DISPATCH_TABLE[fn_name].evaluate(old_node) + new_node = vy_fn.DISPATCH_TABLE[fn_name].fold(old_node) assert contract.foo(left, right) == new_node.value @@ -69,6 +69,6 @@ def foo(a: uint256, b: uint256) -> uint256: vyper_ast = vy_ast.parse_to_ast(f"{fn_name}({left}, {right})") old_node = vyper_ast.body[0].value - new_node = vy_fn.DISPATCH_TABLE[fn_name].evaluate(old_node) + new_node = vy_fn.DISPATCH_TABLE[fn_name].fold(old_node) assert contract.foo(left, right) == new_node.value diff --git a/tests/functional/builtins/folding/test_powmod.py b/tests/functional/builtins/folding/test_powmod.py index 8667ec93fd..f531e77af6 100644 --- a/tests/functional/builtins/folding/test_powmod.py +++ b/tests/functional/builtins/folding/test_powmod.py @@ -21,6 +21,6 @@ def foo(a: uint256, b: uint256) -> uint256: vyper_ast = vy_ast.parse_to_ast(f"pow_mod256({a}, {b})") old_node = vyper_ast.body[0].value - new_node = vy_fn.PowMod256().evaluate(old_node) + new_node = vy_fn.PowMod256().fold(old_node) assert contract.foo(a, b) == new_node.value diff --git a/tests/unit/ast/nodes/test_evaluate_binop_decimal.py b/tests/unit/ast/nodes/test_fold_binop_decimal.py similarity index 97% rename from tests/unit/ast/nodes/test_evaluate_binop_decimal.py rename to tests/unit/ast/nodes/test_fold_binop_decimal.py index 5c9956caba..f20e422574 100644 --- a/tests/unit/ast/nodes/test_evaluate_binop_decimal.py +++ b/tests/unit/ast/nodes/test_fold_binop_decimal.py @@ -31,7 +31,7 @@ def foo(a: decimal, b: decimal) -> decimal: vyper_ast = vy_ast.parse_to_ast(f"{left} {op} {right}") old_node = vyper_ast.body[0].value try: - new_node = old_node.evaluate() + new_node = old_node.fold() is_valid = True except ZeroDivisionException: is_valid = False @@ -48,7 +48,7 @@ def test_binop_pow(): old_node = vyper_ast.body[0].value with pytest.raises(TypeMismatch): - old_node.evaluate() + old_node.fold() @pytest.mark.fuzzing diff --git a/tests/unit/ast/nodes/test_evaluate_binop_int.py b/tests/unit/ast/nodes/test_fold_binop_int.py similarity index 96% rename from tests/unit/ast/nodes/test_evaluate_binop_int.py rename to tests/unit/ast/nodes/test_fold_binop_int.py index 80c9381c0f..62a5ec4d53 100644 --- a/tests/unit/ast/nodes/test_evaluate_binop_int.py +++ b/tests/unit/ast/nodes/test_fold_binop_int.py @@ -27,7 +27,7 @@ def foo(a: int128, b: int128) -> int128: vyper_ast = vy_ast.parse_to_ast(f"{left} {op} {right}") old_node = vyper_ast.body[0].value try: - new_node = old_node.evaluate() + new_node = old_node.fold() is_valid = True except ZeroDivisionException: is_valid = False @@ -56,7 +56,7 @@ def foo(a: uint256, b: uint256) -> uint256: vyper_ast = vy_ast.parse_to_ast(f"{left} {op} {right}") old_node = vyper_ast.body[0].value try: - new_node = old_node.evaluate() + new_node = old_node.fold() is_valid = new_node.value >= 0 except ZeroDivisionException: is_valid = False @@ -83,7 +83,7 @@ def foo(a: uint256, b: uint256) -> uint256: vyper_ast = vy_ast.parse_to_ast(f"{left} ** {right}") old_node = vyper_ast.body[0].value - new_node = old_node.evaluate() + new_node = old_node.fold() assert contract.foo(left, right) == new_node.value diff --git a/tests/unit/ast/nodes/test_evaluate_boolop.py b/tests/unit/ast/nodes/test_fold_boolop.py similarity index 98% rename from tests/unit/ast/nodes/test_evaluate_boolop.py rename to tests/unit/ast/nodes/test_fold_boolop.py index 8b70537c39..5de4b60bda 100644 --- a/tests/unit/ast/nodes/test_evaluate_boolop.py +++ b/tests/unit/ast/nodes/test_fold_boolop.py @@ -26,7 +26,7 @@ def foo({input_value}) -> bool: vyper_ast = vy_ast.parse_to_ast(literal_op) old_node = vyper_ast.body[0].value - new_node = old_node.evaluate() + new_node = old_node.fold() assert contract.foo(*values) == new_node.value diff --git a/tests/unit/ast/nodes/test_evaluate_compare.py b/tests/unit/ast/nodes/test_fold_compare.py similarity index 95% rename from tests/unit/ast/nodes/test_evaluate_compare.py rename to tests/unit/ast/nodes/test_fold_compare.py index 07f8e70de6..735be43cfd 100644 --- a/tests/unit/ast/nodes/test_evaluate_compare.py +++ b/tests/unit/ast/nodes/test_fold_compare.py @@ -21,7 +21,7 @@ def foo(a: int128, b: int128) -> bool: vyper_ast = vy_ast.parse_to_ast(f"{left} {op} {right}") old_node = vyper_ast.body[0].value - new_node = old_node.evaluate() + new_node = old_node.fold() assert contract.foo(left, right) == new_node.value @@ -41,7 +41,7 @@ def foo(a: uint128, b: uint128) -> bool: vyper_ast = vy_ast.parse_to_ast(f"{left} {op} {right}") old_node = vyper_ast.body[0].value - new_node = old_node.evaluate() + new_node = old_node.fold() assert contract.foo(left, right) == new_node.value @@ -65,7 +65,7 @@ def bar(a: int128) -> bool: vyper_ast = vy_ast.parse_to_ast(f"{left} in {right}") old_node = vyper_ast.body[0].value - new_node = old_node.evaluate() + new_node = old_node.fold() # check runtime == fully folded assert contract.foo(left, right) == new_node.value @@ -94,7 +94,7 @@ def bar(a: int128) -> bool: vyper_ast = vy_ast.parse_to_ast(f"{left} not in {right}") old_node = vyper_ast.body[0].value - new_node = old_node.evaluate() + new_node = old_node.fold() # check runtime == fully folded assert contract.foo(left, right) == new_node.value @@ -109,4 +109,4 @@ def test_compare_type_mismatch(op): vyper_ast = vy_ast.parse_to_ast(f"1 {op} 1.0") old_node = vyper_ast.body[0].value with pytest.raises(UnfoldableNode): - old_node.evaluate() + old_node.fold() diff --git a/tests/unit/ast/nodes/test_evaluate_subscript.py b/tests/unit/ast/nodes/test_fold_subscript.py similarity index 94% rename from tests/unit/ast/nodes/test_evaluate_subscript.py rename to tests/unit/ast/nodes/test_fold_subscript.py index ca50a076a5..59a5725b2c 100644 --- a/tests/unit/ast/nodes/test_evaluate_subscript.py +++ b/tests/unit/ast/nodes/test_fold_subscript.py @@ -21,6 +21,6 @@ def foo(array: int128[10], idx: uint256) -> int128: vyper_ast = vy_ast.parse_to_ast(f"{array}[{idx}]") old_node = vyper_ast.body[0].value - new_node = old_node.evaluate() + new_node = old_node.fold() assert contract.foo(array, idx) == new_node.value diff --git a/tests/unit/ast/nodes/test_evaluate_unaryop.py b/tests/unit/ast/nodes/test_fold_unaryop.py similarity index 96% rename from tests/unit/ast/nodes/test_evaluate_unaryop.py rename to tests/unit/ast/nodes/test_fold_unaryop.py index 63d7a0b7ff..dc447955ed 100644 --- a/tests/unit/ast/nodes/test_evaluate_unaryop.py +++ b/tests/unit/ast/nodes/test_fold_unaryop.py @@ -14,7 +14,7 @@ def foo(a: bool) -> bool: vyper_ast = vy_ast.parse_to_ast(f"not {bool_cond}") old_node = vyper_ast.body[0].value - new_node = old_node.evaluate() + new_node = old_node.fold() assert contract.foo(bool_cond) == new_node.value diff --git a/vyper/ast/README.md b/vyper/ast/README.md index 320c69da0c..9979b60cab 100644 --- a/vyper/ast/README.md +++ b/vyper/ast/README.md @@ -82,8 +82,7 @@ folding include: The process of literal folding includes: -1. Foldable node classes are evaluated via their `evaluate` method, which attempts -to create a new `Constant` from the content of the given node. +1. Foldable node classes are evaluated via their `fold` method, which attempts to create a new `Constant` from the content of the given node. 2. Replacement nodes are generated using the `from_node` class method within the new node class. 3. The modification of the tree is handled by `Module.replace_in_tree`, which locates diff --git a/vyper/ast/folding.py b/vyper/ast/folding.py index 51a58f0bb8..9ad969c733 100644 --- a/vyper/ast/folding.py +++ b/vyper/ast/folding.py @@ -44,7 +44,7 @@ def replace_literal_ops(vyper_module: vy_ast.Module) -> int: node_types = (vy_ast.BoolOp, vy_ast.BinOp, vy_ast.UnaryOp, vy_ast.Compare) for node in vyper_module.get_descendants(node_types, reverse=True): try: - new_node = node.evaluate() + new_node = node.fold() except UnfoldableNode: continue @@ -79,7 +79,7 @@ def replace_subscripts(vyper_module: vy_ast.Module) -> int: for node in vyper_module.get_descendants(vy_ast.Subscript, reverse=True): try: - new_node = node.evaluate() + new_node = node.fold() except UnfoldableNode: continue @@ -114,10 +114,10 @@ def replace_builtin_functions(vyper_module: vy_ast.Module) -> int: name = node.func.id func = DISPATCH_TABLE.get(name) - if func is None or not hasattr(func, "evaluate"): + if func is None or not hasattr(func, "fold"): continue try: - new_node = func.evaluate(node) # type: ignore + new_node = func.fold(node) # type: ignore except UnfoldableNode: continue diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index 0596b0c9e9..dbc2e36014 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -366,15 +366,15 @@ def prefold(self) -> Optional["VyperNode"]: """ return None - def evaluate(self) -> "VyperNode": + def fold(self) -> "VyperNode": """ Attempt to evaluate the content of a node and generate a new node from it. - If a node cannot be evaluated it should raise `UnfoldableNode`. This base + If a node cannot be evaluated, it should raise `UnfoldableNode`. This base method acts as a catch-all to raise on any inherited classes that do not implement the method. """ - raise UnfoldableNode(f"{type(self)} cannot be evaluated") + raise UnfoldableNode(f"{type(self)} cannot be folded") def validate(self) -> None: """ @@ -929,7 +929,7 @@ def prefold(self) -> Optional[ExprNode]: return None - def evaluate(self) -> ExprNode: + def fold(self) -> ExprNode: """ Attempt to evaluate the unary operation. @@ -992,7 +992,7 @@ def prefold(self) -> Optional[ExprNode]: value = self.op._op(left.value, right.value) return type(left).from_node(self, value=value) - def evaluate(self) -> ExprNode: + def fold(self) -> ExprNode: """ Attempt to evaluate the arithmetic operation. @@ -1143,7 +1143,7 @@ def prefold(self) -> Optional[ExprNode]: value = self.op._op(values) return NameConstant.from_node(self, value=value) - def evaluate(self) -> ExprNode: + def fold(self) -> ExprNode: """ Attempt to evaluate the boolean operation. @@ -1209,7 +1209,7 @@ def prefold(self) -> Optional[ExprNode]: value = self.op._op(left.value, right.value) return NameConstant.from_node(self, value=value) - def evaluate(self) -> ExprNode: + def fold(self) -> ExprNode: """ Attempt to evaluate the comparison. @@ -1320,7 +1320,7 @@ def prefold(self) -> Optional[ExprNode]: return value.elements[slice_.value] - def evaluate(self) -> ExprNode: + def fold(self) -> ExprNode: """ Attempt to evaluate the subscript. diff --git a/vyper/ast/nodes.pyi b/vyper/ast/nodes.pyi index caa06af918..8fe9600b0b 100644 --- a/vyper/ast/nodes.pyi +++ b/vyper/ast/nodes.pyi @@ -27,7 +27,7 @@ class VyperNode: @classmethod def get_fields(cls: Any) -> set: ... def prefold(self) -> Optional[VyperNode]: ... - def evaluate(self) -> VyperNode: ... + def fold(self) -> VyperNode: ... @classmethod def from_node(cls, node: VyperNode, **kwargs: Any) -> Any: ... def to_dict(self) -> dict: ... diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py index 7fc7716c22..dd428575b2 100644 --- a/vyper/builtins/_signatures.py +++ b/vyper/builtins/_signatures.py @@ -128,11 +128,11 @@ def _validate_arg_types(self, node: vy_ast.Call) -> None: get_exact_type_from_node(arg) def prefold(self, node): - if not hasattr(self, "evaluate"): + if not hasattr(self, "fold"): return None try: - return self.evaluate(node) + return self.fold(node) except (UnfoldableNode, VyperException): return None diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 5ad9b7cfaa..08f5c6f3bb 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -135,7 +135,7 @@ class Floor(BuiltinFunctionT): # TODO: maybe use int136? _return_type = INT256_T - def evaluate(self, node): + def fold(self, node): validate_call_args(node, 1) value = node.args[0]._metadata.get("folded_value") if not isinstance(value, vy_ast.Decimal): @@ -166,7 +166,7 @@ class Ceil(BuiltinFunctionT): # TODO: maybe use int136? _return_type = INT256_T - def evaluate(self, node): + def fold(self, node): validate_call_args(node, 1) value = node.args[0]._metadata.get("folded_value") if not isinstance(value, vy_ast.Decimal): @@ -460,7 +460,7 @@ class Len(BuiltinFunctionT): _inputs = [("b", (StringT.any(), BytesT.any(), DArrayT.any()))] _return_type = UINT256_T - def evaluate(self, node): + def fold(self, node): validate_call_args(node, 1) arg = node.args[0]._metadata.get("folded_value") if isinstance(arg, (vy_ast.Str, vy_ast.Bytes)): @@ -597,7 +597,7 @@ class Keccak256(BuiltinFunctionT): _inputs = [("value", (BytesT.any(), BYTES32_T, StringT.any()))] _return_type = BYTES32_T - def evaluate(self, node): + def fold(self, node): validate_call_args(node, 1) value = node.args[0]._metadata.get("folded_value") if isinstance(value, vy_ast.Bytes): @@ -645,7 +645,7 @@ class Sha256(BuiltinFunctionT): _inputs = [("value", (BYTES32_T, BytesT.any(), StringT.any()))] _return_type = BYTES32_T - def evaluate(self, node): + def fold(self, node): validate_call_args(node, 1) value = node.args[0]._metadata.get("folded_value") if isinstance(value, vy_ast.Bytes): @@ -718,7 +718,7 @@ class MethodID(FoldedFunctionT): _inputs = [("value", StringT.any())] _kwargs = {"output_type": KwargSettings("TYPE_DEFINITION", BytesT(4))} - def evaluate(self, node): + def fold(self, node): validate_call_args(node, 1, ["output_type"]) value = node.args[0]._metadata.get("folded_value") @@ -742,8 +742,8 @@ def fetch_call_return(self, node): return type_ def infer_arg_types(self, node, expected_return_typ=None): - # call `evaluate` for its typechecking side effects - self.evaluate(node) + # call `fold` for its typechecking side effects + self.fold(node) return [self._inputs[0][1]] def infer_kwarg_types(self, node): @@ -993,7 +993,7 @@ def get_denomination(self, node): return denom - def evaluate(self, node): + def fold(self, node): validate_call_args(node, 2) denom = self.get_denomination(node) @@ -1012,9 +1012,9 @@ def fetch_call_return(self, node): return self._return_type def infer_arg_types(self, node, expected_return_typ=None): - # call `evaluate` for its typechecking side effects` + # call `fold` for its typechecking side effects` try: - self.evaluate(node) + self.fold(node) except UnfoldableNode: pass @@ -1350,7 +1350,7 @@ class BitwiseAnd(BuiltinFunctionT): _return_type = UINT256_T _warned = False - def evaluate(self, node): + def fold(self, node): if not self.__class__._warned: vyper_warn("`bitwise_and()` is deprecated! Please use the & operator instead.") self.__class__._warned = True @@ -1375,7 +1375,7 @@ class BitwiseOr(BuiltinFunctionT): _return_type = UINT256_T _warned = False - def evaluate(self, node): + def fold(self, node): if not self.__class__._warned: vyper_warn("`bitwise_or()` is deprecated! Please use the | operator instead.") self.__class__._warned = True @@ -1400,7 +1400,7 @@ class BitwiseXor(BuiltinFunctionT): _return_type = UINT256_T _warned = False - def evaluate(self, node): + def fold(self, node): if not self.__class__._warned: vyper_warn("`bitwise_xor()` is deprecated! Please use the ^ operator instead.") self.__class__._warned = True @@ -1425,7 +1425,7 @@ class BitwiseNot(BuiltinFunctionT): _return_type = UINT256_T _warned = False - def evaluate(self, node): + def fold(self, node): if not self.__class__._warned: vyper_warn("`bitwise_not()` is deprecated! Please use the ~ operator instead.") self.__class__._warned = True @@ -1451,7 +1451,7 @@ class Shift(BuiltinFunctionT): _return_type = UINT256_T _warned = False - def evaluate(self, node): + def fold(self, node): if not self.__class__._warned: vyper_warn("`shift()` is deprecated! Please use the << or >> operator instead.") self.__class__._warned = True @@ -1502,7 +1502,7 @@ class _AddMulMod(BuiltinFunctionT): _inputs = [("a", UINT256_T), ("b", UINT256_T), ("c", UINT256_T)] _return_type = UINT256_T - def evaluate(self, node): + def fold(self, node): validate_call_args(node, 3) args = [i._metadata.get("folded_value") for i in node.args] if isinstance(args[2], vy_ast.Int) and args[2].value == 0: @@ -1543,7 +1543,7 @@ class PowMod256(BuiltinFunctionT): _inputs = [("a", UINT256_T), ("b", UINT256_T)] _return_type = UINT256_T - def evaluate(self, node): + def fold(self, node): validate_call_args(node, 2) values = [i._metadata.get("folded_value") for i in node.args] if any(not isinstance(i, vy_ast.Int) for i in values): @@ -1564,7 +1564,7 @@ class Abs(BuiltinFunctionT): _inputs = [("value", INT256_T)] _return_type = INT256_T - def evaluate(self, node): + def fold(self, node): validate_call_args(node, 1) value = node.args[0]._metadata.get("folded_value") if not isinstance(value, vy_ast.Int): @@ -2003,7 +2003,7 @@ class UnsafeDiv(_UnsafeMath): class _MinMax(BuiltinFunctionT): _inputs = [("a", (DecimalT(), IntegerT.any())), ("b", (DecimalT(), IntegerT.any()))] - def evaluate(self, node): + def fold(self, node): validate_call_args(node, 2) left = node.args[0]._metadata.get("folded_value") @@ -2081,7 +2081,7 @@ def fetch_call_return(self, node): len_needed = math.ceil(bits * math.log(2) / math.log(10)) return StringT(len_needed) - def evaluate(self, node): + def fold(self, node): validate_call_args(node, 1) value = node.args[0]._metadata.get("folded_value") if not isinstance(value, vy_ast.Int): @@ -2569,7 +2569,7 @@ def build_IR(self, expr, args, kwargs, context): class _MinMaxValue(TypenameFoldedFunctionT): - def evaluate(self, node): + def fold(self, node): self._validate_arg_types(node) input_type = type_from_annotation(node.args[0]) @@ -2588,8 +2588,8 @@ def evaluate(self, node): return ret def infer_arg_types(self, node, expected_return_typ=None): - # call `evaluate` for its typechecking side effects - self.evaluate(node) + # call `fold` for its typechecking side effects + self.fold(node) input_typedef = TYPE_T(type_from_annotation(node.args[0])) return [input_typedef] @@ -2611,7 +2611,7 @@ def _eval(self, type_): class Epsilon(TypenameFoldedFunctionT): _id = "epsilon" - def evaluate(self, node): + def fold(self, node): self._validate_arg_types(node) input_type = type_from_annotation(node.args[0]) From 83a8b66f24eb4272f17c12e510824d05cba0a0a9 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 22 Dec 2023 13:33:28 +0800 Subject: [PATCH 090/120] add immutable enum to modifiability --- vyper/builtins/_signatures.py | 4 ++-- vyper/semantics/analysis/base.py | 16 +++++++++------- vyper/semantics/analysis/local.py | 8 ++++---- vyper/semantics/analysis/module.py | 10 +++++----- vyper/semantics/analysis/utils.py | 6 +++--- vyper/semantics/environment.py | 4 ++-- vyper/semantics/types/function.py | 4 ++-- 7 files changed, 27 insertions(+), 25 deletions(-) diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py index dd428575b2..616dd39a07 100644 --- a/vyper/builtins/_signatures.py +++ b/vyper/builtins/_signatures.py @@ -6,7 +6,7 @@ from vyper.codegen.expr import Expr from vyper.codegen.ir_node import IRnode from vyper.exceptions import CompilerPanic, TypeMismatch, UnfoldableNode, VyperException -from vyper.semantics.analysis.base import VariableConstancy +from vyper.semantics.analysis.base import Modifiability from vyper.semantics.analysis.utils import ( check_variable_constancy, get_exact_type_from_node, @@ -112,7 +112,7 @@ def _validate_arg_types(self, node: vy_ast.Call) -> None: for kwarg in node.keywords: kwarg_settings = self._kwargs[kwarg.arg] if kwarg_settings.require_literal and not check_variable_constancy( - kwarg.value, VariableConstancy.RUNTIME_CONSTANT + kwarg.value, Modifiability.IMMUTABLE ): raise TypeMismatch("Value must be literal or environment variable", kwarg.value) self._validate_single(kwarg.value, kwarg_settings.typ) diff --git a/vyper/semantics/analysis/base.py b/vyper/semantics/analysis/base.py index 1d293dc149..a34552adb7 100644 --- a/vyper/semantics/analysis/base.py +++ b/vyper/semantics/analysis/base.py @@ -97,10 +97,12 @@ def from_abi(cls, abi_dict: Dict) -> "StateMutability": # specifying a state mutability modifier at all. Do the same here. -class VariableConstancy(enum.IntEnum): - MUTABLE = enum.auto() - RUNTIME_CONSTANT = enum.auto() - COMPILE_TIME_CONSTANT = enum.auto() +class Modifiability(enum.IntEnum): + MODIFIABLE = enum.auto() + IMMUTABLE = enum.auto() + NOT_MODIFIABLE = enum.auto() + CONSTANT_IN_CURRENT_TX = enum.auto() + ALWAYS_CONSTANT = enum.auto() class DataPosition: @@ -198,7 +200,7 @@ class VarInfo: typ: VyperType location: DataLocation = DataLocation.UNSET - constancy: VariableConstancy = VariableConstancy.MUTABLE + constancy: Modifiability = Modifiability.MODIFIABLE is_public: bool = False is_immutable: bool = False is_transient: bool = False @@ -231,7 +233,7 @@ class ExprInfo: typ: VyperType var_info: Optional[VarInfo] = None location: DataLocation = DataLocation.UNSET - constancy: VariableConstancy = VariableConstancy.MUTABLE + constancy: Modifiability = Modifiability.MODIFIABLE is_immutable: bool = False def __post_init__(self): @@ -283,7 +285,7 @@ def validate_modification(self, node: vy_ast.VyperNode, mutability: StateMutabil if self.location == DataLocation.CALLDATA: raise ImmutableViolation("Cannot write to calldata", node) - if self.constancy == VariableConstancy.COMPILE_TIME_CONSTANT: + if self.constancy == Modifiability.ALWAYS_CONSTANT: raise ImmutableViolation("Constant value cannot be written to", node) if self.is_immutable: if node.get_ancestor(vy_ast.FunctionDef).get("name") != "__init__": diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 38a0ef8255..143ab41fd4 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -19,7 +19,7 @@ VariableDeclarationException, VyperException, ) -from vyper.semantics.analysis.base import VariableConstancy, VarInfo +from vyper.semantics.analysis.base import Modifiability, VarInfo from vyper.semantics.analysis.common import VyperNodeVisitorBase from vyper.semantics.analysis.utils import ( get_common_types, @@ -192,9 +192,9 @@ def __init__( def analyze(self): # allow internal function params to be mutable location, is_immutable, constancy = ( - (DataLocation.MEMORY, False, VariableConstancy.MUTABLE) + (DataLocation.MEMORY, False, Modifiability.MODIFIABLE) if self.func.is_internal - else (DataLocation.CALLDATA, True, VariableConstancy.RUNTIME_CONSTANT) + else (DataLocation.CALLDATA, True, Modifiability.NOT_MODIFIABLE) ) for arg in self.func.arguments: self.namespace[arg.name] = VarInfo( @@ -494,7 +494,7 @@ def visit_For(self, node): with self.namespace.enter_scope(): self.namespace[iter_name] = VarInfo( - possible_target_type, constancy=VariableConstancy.COMPILE_TIME_CONSTANT + possible_target_type, constancy=Modifiability.ALWAYS_CONSTANT ) try: diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index 0b57d4045f..05cdf7dac6 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -20,7 +20,7 @@ VariableDeclarationException, VyperException, ) -from vyper.semantics.analysis.base import ImportInfo, ModuleInfo, VariableConstancy, VarInfo +from vyper.semantics.analysis.base import ImportInfo, Modifiability, ModuleInfo, VarInfo from vyper.semantics.analysis.common import VyperNodeVisitorBase from vyper.semantics.analysis.import_graph import ImportGraph from vyper.semantics.analysis.local import ExprVisitor, validate_functions @@ -263,11 +263,11 @@ def visit_VariableDecl(self, node): ) constancy = ( - VariableConstancy.RUNTIME_CONSTANT + Modifiability.IMMUTABLE if node.is_immutable - else VariableConstancy.COMPILE_TIME_CONSTANT + else Modifiability.ALWAYS_CONSTANT if node.is_constant - else VariableConstancy.MUTABLE + else Modifiability.MODIFIABLE ) type_ = type_from_annotation(node.annotation, data_loc) @@ -317,7 +317,7 @@ def _validate_self_namespace(): ExprVisitor().visit(node.value, type_) - if not check_variable_constancy(node.value, VariableConstancy.COMPILE_TIME_CONSTANT): + if not check_variable_constancy(node.value, Modifiability.ALWAYS_CONSTANT): raise StateAccessViolation("Value must be a literal", node.value) validate_expected_type(node.value, type_) diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index d5555f3888..6cb6fb3141 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -17,7 +17,7 @@ ZeroDivisionException, ) from vyper.semantics import types -from vyper.semantics.analysis.base import ExprInfo, ModuleInfo, VariableConstancy, VarInfo +from vyper.semantics.analysis.base import ExprInfo, Modifiability, ModuleInfo, VarInfo from vyper.semantics.analysis.levenshtein_utils import get_levenshtein_error_suggestions from vyper.semantics.namespace import get_namespace from vyper.semantics.types.base import TYPE_T, VyperType @@ -201,7 +201,7 @@ def _raise_invalid_reference(name, node): if isinstance(s, (VyperType, TYPE_T)): # ex. foo.bar(). bar() is a ContractFunctionT return [s] - if is_self_reference and s.constancy >= VariableConstancy.RUNTIME_CONSTANT: + if is_self_reference and s.constancy >= Modifiability.IMMUTABLE: _raise_invalid_reference(name, node) # general case. s is a VarInfo, e.g. self.foo return [s.typ] @@ -639,7 +639,7 @@ def _check_literal(node: vy_ast.VyperNode) -> bool: return False -def check_variable_constancy(node: vy_ast.VyperNode, constancy: VariableConstancy) -> bool: +def check_variable_constancy(node: vy_ast.VyperNode, constancy: Modifiability) -> bool: """ Check if the given node is a literal or constant value. """ diff --git a/vyper/semantics/environment.py b/vyper/semantics/environment.py index 937237ad0b..fb89d6dab7 100644 --- a/vyper/semantics/environment.py +++ b/vyper/semantics/environment.py @@ -1,6 +1,6 @@ from typing import Dict -from vyper.semantics.analysis.base import VariableConstancy, VarInfo +from vyper.semantics.analysis.base import Modifiability, VarInfo from vyper.semantics.types import AddressT, BytesT, VyperType from vyper.semantics.types.shortcuts import BYTES32_T, UINT256_T @@ -52,7 +52,7 @@ def get_constant_vars() -> Dict: """ result = {} for k, v in CONSTANT_ENVIRONMENT_VARS.items(): - result[k] = VarInfo(v, constancy=VariableConstancy.RUNTIME_CONSTANT) + result[k] = VarInfo(v, constancy=Modifiability.CONSTANT_IN_CURRENT_TX) return result diff --git a/vyper/semantics/types/function.py b/vyper/semantics/types/function.py index 8ca12bc749..e3001c6c91 100644 --- a/vyper/semantics/types/function.py +++ b/vyper/semantics/types/function.py @@ -18,9 +18,9 @@ ) from vyper.semantics.analysis.base import ( FunctionVisibility, + Modifiability, StateMutability, StorageSlot, - VariableConstancy, ) from vyper.semantics.analysis.utils import ( check_variable_constancy, @@ -702,7 +702,7 @@ def _parse_args( positional_args.append(PositionalArg(argname, type_, ast_source=arg)) else: value = funcdef.args.defaults[i - n_positional_args] - if not check_variable_constancy(value, VariableConstancy.RUNTIME_CONSTANT): + if not check_variable_constancy(value, Modifiability.IMMUTABLE): raise StateAccessViolation("Value must be literal or environment variable", value) validate_expected_type(value, type_) keyword_args.append(KeywordArg(argname, type_, value, ast_source=arg)) From 59d2a04fba49ab031c1d1db2b37a1a14c4dcf63b Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 22 Dec 2023 15:32:37 +0800 Subject: [PATCH 091/120] add _is_prefoldable attribute to nodes --- vyper/ast/nodes.py | 10 +++++++++- vyper/semantics/analysis/pre_typecheck.py | 15 ++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index dbc2e36014..6ab1bb96e1 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -208,11 +208,13 @@ class VyperNode: _description : str, optional A human-readable description of the node. Used to give more verbose error messages. + _is_prefoldable : str, optional + If `True`, indicates that pre-folding should be attempted on the node. _only_empty_fields : Tuple, optional Field names that, if present, must be set to None or a `SyntaxException` is raised. This attribute is used to exclude syntax that is valid in Python but not in Vyper. - _terminus : bool, optional + _is_terminus : bool, optional If `True`, indicates that execution halts upon reaching this node. _translated_fields : Dict, optional Field names that are reassigned if encountered. Used to normalize fields @@ -883,6 +885,7 @@ def s(self): class List(ExprNode): __slots__ = ("elements",) + _is_prefoldable = True _translated_fields = {"elts": "elements"} def prefold(self) -> Optional[ExprNode]: @@ -920,6 +923,7 @@ class Name(ExprNode): class UnaryOp(ExprNode): __slots__ = ("op", "operand") + _is_prefoldable = True def prefold(self) -> Optional[ExprNode]: operand = self.operand._metadata.get("folded_value") @@ -975,6 +979,7 @@ def _op(self, value): class BinOp(ExprNode): __slots__ = ("left", "op", "right") + _is_prefoldable = True def prefold(self) -> Optional[ExprNode]: left = self.left._metadata.get("folded_value") @@ -1134,6 +1139,7 @@ class RShift(Operator): class BoolOp(ExprNode): __slots__ = ("op", "values") + _is_prefoldable = True def prefold(self) -> Optional[ExprNode]: values = [i._metadata.get("folded_value") for i in self.values] @@ -1190,6 +1196,7 @@ class Compare(ExprNode): """ __slots__ = ("left", "op", "right") + _is_prefoldable = True def __init__(self, *args, **kwargs): if len(kwargs["ops"]) > 1 or len(kwargs["comparators"]) > 1: @@ -1310,6 +1317,7 @@ class Attribute(ExprNode): class Subscript(ExprNode): __slots__ = ("slice", "value") + _is_prefoldable = True def prefold(self) -> Optional[ExprNode]: slice_ = self.slice.value._metadata.get("folded_value") diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index 6564440c1e..797fe3bbd3 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -46,23 +46,15 @@ def pre_typecheck(node: vy_ast.Module) -> None: def prefold(node: vy_ast.VyperNode, constants: dict[str, vy_ast.VyperNode]) -> None: - if isinstance( - node, - ( - vy_ast.BinOp, - vy_ast.BoolOp, - vy_ast.Compare, - vy_ast.List, - vy_ast.Subscript, - vy_ast.UnaryOp, - ), - ): + if getattr(node, "_is_prefoldable", None): node._metadata["folded_value"] = node.prefold() + return if isinstance(node, vy_ast.Name): var_name = node.id if var_name in constants: node._metadata["folded_value"] = constants[var_name] + return if isinstance(node, vy_ast.Call): if isinstance(node.func, vy_ast.Name): @@ -73,3 +65,4 @@ def prefold(node: vy_ast.VyperNode, constants: dict[str, vy_ast.VyperNode]) -> N call_type = DISPATCH_TABLE.get(func_name) if call_type: node._metadata["folded_value"] = call_type.prefold(node) # type: ignore + return From 7898efeda2d106dafa874eca6dd39b9d9a1c51d0 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Dec 2023 11:13:19 +0800 Subject: [PATCH 092/120] remove prefold; add get_folded_value and get_folded_value_maybe --- vyper/ast/nodes.py | 117 +++++++++------------- vyper/ast/nodes.pyi | 3 +- vyper/builtins/_signatures.py | 14 +-- vyper/semantics/analysis/pre_typecheck.py | 23 +++-- vyper/semantics/types/utils.py | 2 +- 5 files changed, 67 insertions(+), 92 deletions(-) diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index 1393bc7d15..2eede3f24e 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -375,15 +375,26 @@ def description(self): """ return getattr(self, "_description", type(self).__name__) - def prefold(self) -> Optional["VyperNode"]: + def get_folded_value(self) -> "VyperNode": """ - Attempt to evaluate the content of a node and generate a new node from it, - allowing for values that may be out of bounds during semantics typechecking. + Attempt to get the folded value and cache it on `_metadata["folded_value"]`. + Raises UnfoldableNode if not. + """ + if "folded_value" not in self._metadata: + self._metadata["folded_value"] = self.fold() + return self._metadata["folded_value"] - If a node cannot be prefolded, it should return None. This base method acts - as a catch-call for all inherited classes that do not implement the method. + def get_folded_value_maybe(self) -> Optional["VyperNode"]: """ - return None + Attempt to get the folded value and cache it on `_metadata["folded_value"]`. + Returns None if not. + """ + if "folded_value" not in self._metadata: + try: + self._metadata["folded_value"] = self.fold() + except (UnfoldableNode, VyperException): + return None + return self._metadata["folded_value"] def fold(self) -> "VyperNode": """ @@ -905,8 +916,8 @@ class List(ExprNode): _is_prefoldable = True _translated_fields = {"elts": "elements"} - def prefold(self) -> Optional[ExprNode]: - elements = [e._metadata.get("folded_value") for e in self.elements] + def fold(self) -> Optional[ExprNode]: + elements = [e.get_folded_value_maybe() for e in self.elements] if None not in elements: return type(self).from_node(self, elements=elements) @@ -942,14 +953,6 @@ class UnaryOp(ExprNode): __slots__ = ("op", "operand") _is_prefoldable = True - def prefold(self) -> Optional[ExprNode]: - operand = self.operand._metadata.get("folded_value") - if operand is not None: - value = self.op._op(operand.value) - return type(operand).from_node(self, value=value) - - return None - def fold(self) -> ExprNode: """ Attempt to evaluate the unary operation. @@ -959,14 +962,16 @@ def fold(self) -> ExprNode: Int | Decimal Node representing the result of the evaluation. """ - if isinstance(self.op, Not) and not isinstance(self.operand, NameConstant): + operand = self.operand.get_folded_value_maybe() + + if isinstance(self.op, Not) and not isinstance(operand, NameConstant): raise UnfoldableNode("Node contains invalid field(s) for evaluation") - if isinstance(self.op, USub) and not isinstance(self.operand, (Int, Decimal)): + if isinstance(self.op, USub) and not isinstance(operand, (Int, Decimal)): raise UnfoldableNode("Node contains invalid field(s) for evaluation") - if isinstance(self.op, Invert) and not isinstance(self.operand, Int): + if isinstance(self.op, Invert) and not isinstance(operand, Int): raise UnfoldableNode("Node contains invalid field(s) for evaluation") - value = self.op._op(self.operand.value) + value = self.op._op(operand.value) return type(self.operand).from_node(self, value=value) @@ -998,22 +1003,6 @@ class BinOp(ExprNode): __slots__ = ("left", "op", "right") _is_prefoldable = True - def prefold(self) -> Optional[ExprNode]: - left = self.left._metadata.get("folded_value") - right = self.right._metadata.get("folded_value") - - if None in (left, right): - return None - - # this validation is performed to prevent the compiler from hanging - # on very large shifts and improve the error message for negative - # values. - if isinstance(self.op, (LShift, RShift)) and not (0 <= right.value <= 256): - raise InvalidLiteral("Shift bits must be between 0 and 256", self.right) - - value = self.op._op(left.value, right.value) - return type(left).from_node(self, value=value) - def fold(self) -> ExprNode: """ Attempt to evaluate the arithmetic operation. @@ -1023,12 +1012,18 @@ def fold(self) -> ExprNode: Int | Decimal Node representing the result of the evaluation. """ - left, right = self.left, self.right + left, right = [i.get_folded_value_maybe() for i in (self.left, self.right)] if type(left) is not type(right): raise UnfoldableNode("Node contains invalid field(s) for evaluation") if not isinstance(left, (Int, Decimal)): raise UnfoldableNode("Node contains invalid field(s) for evaluation") + # this validation is performed to prevent the compiler from hanging + # on very large shifts and improve the error message for negative + # values. + if isinstance(self.op, (LShift, RShift)) and not (0 <= right.value <= 256): + raise InvalidLiteral("Shift bits must be between 0 and 256", self.right) + value = self.op._op(left.value, right.value) return type(left).from_node(self, value=value) @@ -1158,14 +1153,6 @@ class BoolOp(ExprNode): __slots__ = ("op", "values") _is_prefoldable = True - def prefold(self) -> Optional[ExprNode]: - values = [i._metadata.get("folded_value") for i in self.values] - if None in values: - return None - - value = self.op._op(values) - return NameConstant.from_node(self, value=value) - def fold(self) -> ExprNode: """ Attempt to evaluate the boolean operation. @@ -1175,13 +1162,12 @@ def fold(self) -> ExprNode: NameConstant Node representing the result of the evaluation. """ - if next((i for i in self.values if not isinstance(i, NameConstant)), None): - raise UnfoldableNode("Node contains invalid field(s) for evaluation") + values = [i.get_folded_value_maybe() for i in self.values] - values = [i.value for i in self.values] - if None in values: + if any(not isinstance(i, NameConstant) for i in values): raise UnfoldableNode("Node contains invalid field(s) for evaluation") + values = [i.value for i in values] value = self.op._op(values) return NameConstant.from_node(self, value=value) @@ -1223,16 +1209,6 @@ def __init__(self, *args, **kwargs): kwargs["right"] = kwargs.pop("comparators")[0] super().__init__(*args, **kwargs) - def prefold(self) -> Optional[ExprNode]: - left = self.left._metadata.get("folded_value") - right = self.right._metadata.get("folded_value") - - if None in (left, right): - return None - - value = self.op._op(left.value, right.value) - return NameConstant.from_node(self, value=value) - def fold(self) -> ExprNode: """ Attempt to evaluate the comparison. @@ -1242,7 +1218,7 @@ def fold(self) -> ExprNode: NameConstant Node representing the result of the evaluation. """ - left, right = self.left, self.right + left, right = [i.get_folded_value_maybe() for i in (self.left, self.right)] if not isinstance(left, Constant): raise UnfoldableNode("Node contains invalid field(s) for evaluation") @@ -1336,15 +1312,6 @@ class Subscript(ExprNode): __slots__ = ("slice", "value") _is_prefoldable = True - def prefold(self) -> Optional[ExprNode]: - slice_ = self.slice.value._metadata.get("folded_value") - value = self.value._metadata.get("folded_value") - - if None in (slice_, value): - return None - - return value.elements[slice_.value] - def fold(self) -> ExprNode: """ Attempt to evaluate the subscript. @@ -1357,12 +1324,18 @@ def fold(self) -> ExprNode: ExprNode Node representing the result of the evaluation. """ - if not isinstance(self.value, List): + slice_ = self.slice.value.get_folded_value_maybe() + value = self.value.get_folded_value_maybe() + + if not isinstance(value, List): raise UnfoldableNode("Subscript object is not a literal list") - elements = self.value.elements + elements = value.elements if len(set([type(i) for i in elements])) > 1: raise UnfoldableNode("List contains multiple node types") - idx = self.slice.get("value.value") + + if not isinstance(slice_, Int): + raise UnfoldableNode("Node contains invalid field(s) for evaluation") + idx = slice_.value if not isinstance(idx, int) or idx < 0 or idx >= len(elements): raise UnfoldableNode("Invalid index value") diff --git a/vyper/ast/nodes.pyi b/vyper/ast/nodes.pyi index e5b62f17c1..a57db549a4 100644 --- a/vyper/ast/nodes.pyi +++ b/vyper/ast/nodes.pyi @@ -26,7 +26,8 @@ class VyperNode: def description(self): ... @classmethod def get_fields(cls: Any) -> set: ... - def prefold(self) -> Optional[VyperNode]: ... + def get_folded_value(self) -> VyperNode: ... + def get_folded_value_maybe(self) -> Optional[VyperNode]: ... def fold(self) -> VyperNode: ... @classmethod def from_node(cls, node: VyperNode, **kwargs: Any) -> Any: ... diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py index 616dd39a07..9629510c79 100644 --- a/vyper/builtins/_signatures.py +++ b/vyper/builtins/_signatures.py @@ -5,7 +5,7 @@ from vyper.ast.validation import validate_call_args from vyper.codegen.expr import Expr from vyper.codegen.ir_node import IRnode -from vyper.exceptions import CompilerPanic, TypeMismatch, UnfoldableNode, VyperException +from vyper.exceptions import CompilerPanic, TypeMismatch, UnfoldableNode from vyper.semantics.analysis.base import Modifiability from vyper.semantics.analysis.utils import ( check_variable_constancy, @@ -127,20 +127,14 @@ def _validate_arg_types(self, node: vy_ast.Call) -> None: # ensures the type can be inferred exactly. get_exact_type_from_node(arg) - def prefold(self, node): - if not hasattr(self, "fold"): - return None - - try: - return self.fold(node) - except (UnfoldableNode, VyperException): - return None - def fetch_call_return(self, node: vy_ast.Call) -> Optional[VyperType]: self._validate_arg_types(node) return self._return_type + def fold(self, node: vy_ast.Call) -> vy_ast.VyperNode: + raise UnfoldableNode(f"{type(self)} cannot be folded") + def infer_arg_types(self, node: vy_ast.Call, expected_return_typ=None) -> list[VyperType]: self._validate_arg_types(node) ret = [expected for (_, expected) in self._inputs] diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index 797fe3bbd3..426b377a9d 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -1,4 +1,5 @@ from vyper import ast as vy_ast +from vyper.exceptions import UnfoldableNode, VyperException def get_constants(node: vy_ast.Module) -> dict: @@ -45,11 +46,7 @@ def pre_typecheck(node: vy_ast.Module) -> None: prefold(n, constants) -def prefold(node: vy_ast.VyperNode, constants: dict[str, vy_ast.VyperNode]) -> None: - if getattr(node, "_is_prefoldable", None): - node._metadata["folded_value"] = node.prefold() - return - +def prefold(node: vy_ast.VyperNode, constants: dict[str, vy_ast.VyperNode]): if isinstance(node, vy_ast.Name): var_name = node.id if var_name in constants: @@ -63,6 +60,16 @@ def prefold(node: vy_ast.VyperNode, constants: dict[str, vy_ast.VyperNode]) -> N func_name = node.func.id call_type = DISPATCH_TABLE.get(func_name) - if call_type: - node._metadata["folded_value"] = call_type.prefold(node) # type: ignore - return + if call_type and hasattr(call_type, "fold"): + try: + node._metadata["folded_value"] = call_type.fold(node) + return + except (UnfoldableNode, VyperException): + pass + + if getattr(node, "_is_prefoldable", None): + try: + # call `get_folded_value`` for its side effects + node.get_folded_value() + except (UnfoldableNode, VyperException): + pass diff --git a/vyper/semantics/types/utils.py b/vyper/semantics/types/utils.py index 0981265dc2..137d8d56f5 100644 --- a/vyper/semantics/types/utils.py +++ b/vyper/semantics/types/utils.py @@ -179,7 +179,7 @@ def get_index_value(node: vy_ast.Index) -> int: # TODO: revisit this! from vyper.semantics.analysis.utils import get_possible_types_from_node - value = node.value._metadata.get("folded_value") + value = node.value.get_folded_value_maybe() if not isinstance(value, vy_ast.Int): if hasattr(node, "value"): # even though the subscript is an invalid type, first check if it's a valid _something_ From 3b6a56621ac7230a9bd3f6a7292d06e360bd0bb2 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Dec 2023 12:29:49 +0800 Subject: [PATCH 093/120] uncatch vy exc in prefold --- vyper/semantics/analysis/pre_typecheck.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index 426b377a9d..3be2e16657 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -71,5 +71,5 @@ def prefold(node: vy_ast.VyperNode, constants: dict[str, vy_ast.VyperNode]): try: # call `get_folded_value`` for its side effects node.get_folded_value() - except (UnfoldableNode, VyperException): + except UnfoldableNode: pass From 51d934441914738efaea5a1f108d16640bf601f8 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Dec 2023 13:33:18 +0800 Subject: [PATCH 094/120] fix unary fold node type --- vyper/ast/nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index 2eede3f24e..152ec75a5d 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -972,7 +972,7 @@ def fold(self) -> ExprNode: raise UnfoldableNode("Node contains invalid field(s) for evaluation") value = self.op._op(operand.value) - return type(self.operand).from_node(self, value=value) + return type(operand).from_node(self, value=value) class Operator(VyperNode): From 64214ec91c91d2405a3b521972239ed4598b019a Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Dec 2023 13:34:44 +0800 Subject: [PATCH 095/120] fix bool test --- tests/functional/syntax/test_bool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/syntax/test_bool.py b/tests/functional/syntax/test_bool.py index 5388a92b95..48ed37321a 100644 --- a/tests/functional/syntax/test_bool.py +++ b/tests/functional/syntax/test_bool.py @@ -37,7 +37,7 @@ def foo(): def foo() -> bool: return (1 == 2) <= (1 == 1) """, - InvalidOperation, + TypeMismatch, ), """ @external From 1d3de4bbaca343bcd68e14e4ae4916ec2f0f31df Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Dec 2023 13:38:45 +0800 Subject: [PATCH 096/120] rename get_folded_value to get_folded_value_throwing --- vyper/ast/nodes.py | 2 +- vyper/ast/nodes.pyi | 2 +- vyper/semantics/analysis/pre_typecheck.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index 152ec75a5d..e9d4482b01 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -375,7 +375,7 @@ def description(self): """ return getattr(self, "_description", type(self).__name__) - def get_folded_value(self) -> "VyperNode": + def get_folded_value_throwing(self) -> "VyperNode": """ Attempt to get the folded value and cache it on `_metadata["folded_value"]`. Raises UnfoldableNode if not. diff --git a/vyper/ast/nodes.pyi b/vyper/ast/nodes.pyi index a57db549a4..7531a6d02c 100644 --- a/vyper/ast/nodes.pyi +++ b/vyper/ast/nodes.pyi @@ -26,7 +26,7 @@ class VyperNode: def description(self): ... @classmethod def get_fields(cls: Any) -> set: ... - def get_folded_value(self) -> VyperNode: ... + def get_folded_value_throwing(self) -> VyperNode: ... def get_folded_value_maybe(self) -> Optional[VyperNode]: ... def fold(self) -> VyperNode: ... @classmethod diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index 3be2e16657..e887287626 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -70,6 +70,6 @@ def prefold(node: vy_ast.VyperNode, constants: dict[str, vy_ast.VyperNode]): if getattr(node, "_is_prefoldable", None): try: # call `get_folded_value`` for its side effects - node.get_folded_value() + node.get_folded_value_throwing() except UnfoldableNode: pass From 1d8b5bed7856c2a0cbf8ce21a732fe65d386d8a2 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Dec 2023 13:41:04 +0800 Subject: [PATCH 097/120] use maybe for side efx in prefold --- vyper/semantics/analysis/pre_typecheck.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index e887287626..203937c759 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -68,8 +68,5 @@ def prefold(node: vy_ast.VyperNode, constants: dict[str, vy_ast.VyperNode]): pass if getattr(node, "_is_prefoldable", None): - try: - # call `get_folded_value`` for its side effects - node.get_folded_value_throwing() - except UnfoldableNode: - pass + # call `get_folded_value_maybe` for its side effects + node.get_folded_value_maybe() From 6e3ad8ee105af5fcd3b121fb1804141c96c5dc3e Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Dec 2023 13:53:03 +0800 Subject: [PATCH 098/120] replace get with maybe variant --- vyper/builtins/functions.py | 42 +++++++++++------------ vyper/semantics/analysis/local.py | 12 +++---- vyper/semantics/analysis/pre_typecheck.py | 2 +- vyper/semantics/analysis/utils.py | 2 +- vyper/semantics/types/subscriptable.py | 2 +- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 08f5c6f3bb..9978670f71 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -137,7 +137,7 @@ class Floor(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 1) - value = node.args[0]._metadata.get("folded_value") + value = node.args[0].get_folded_value_maybe() if not isinstance(value, vy_ast.Decimal): raise UnfoldableNode @@ -168,7 +168,7 @@ class Ceil(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 1) - value = node.args[0]._metadata.get("folded_value") + value = node.args[0].get_folded_value_maybe() if not isinstance(value, vy_ast.Decimal): raise UnfoldableNode @@ -462,7 +462,7 @@ class Len(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 1) - arg = node.args[0]._metadata.get("folded_value") + arg = node.args[0].get_folded_value_maybe() if isinstance(arg, (vy_ast.Str, vy_ast.Bytes)): length = len(arg.value) elif isinstance(arg, vy_ast.Hex): @@ -599,7 +599,7 @@ class Keccak256(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 1) - value = node.args[0]._metadata.get("folded_value") + value = node.args[0].get_folded_value_maybe() if isinstance(value, vy_ast.Bytes): value = value.value elif isinstance(value, vy_ast.Str): @@ -647,7 +647,7 @@ class Sha256(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 1) - value = node.args[0]._metadata.get("folded_value") + value = node.args[0].get_folded_value_maybe() if isinstance(value, vy_ast.Bytes): value = value.value elif isinstance(value, vy_ast.Str): @@ -721,7 +721,7 @@ class MethodID(FoldedFunctionT): def fold(self, node): validate_call_args(node, 1, ["output_type"]) - value = node.args[0]._metadata.get("folded_value") + value = node.args[0].get_folded_value_maybe() if not isinstance(value, vy_ast.Str): raise InvalidType("method id must be given as a literal string", node.args[0]) if " " in value.value: @@ -981,7 +981,7 @@ class AsWeiValue(BuiltinFunctionT): } def get_denomination(self, node): - value = node.args[1]._metadata.get("folded_value") + value = node.args[1].get_folded_value_maybe() if not isinstance(value, vy_ast.Str): raise ArgumentException( "Wei denomination must be given as a literal string", node.args[1] @@ -997,7 +997,7 @@ def fold(self, node): validate_call_args(node, 2) denom = self.get_denomination(node) - value = node.args[0]._metadata.get("folded_value") + value = node.args[0].get_folded_value_maybe() if not isinstance(value, (vy_ast.Decimal, vy_ast.Int)): raise UnfoldableNode value = value.value @@ -1083,10 +1083,10 @@ def fetch_call_return(self, node): outsize = kwargz.get("max_outsize") if outsize is not None: - outsize = outsize._metadata.get("folded_value") + outsize = outsize.get_folded_value_maybe() revert_on_failure = kwargz.get("revert_on_failure") if revert_on_failure is not None: - revert_on_failure = revert_on_failure._metadata.get("folded_value") + revert_on_failure = revert_on_failure.get_folded_value_maybe() revert_on_failure = revert_on_failure.value if revert_on_failure is not None else True if outsize is None or outsize.value == 0: @@ -1356,7 +1356,7 @@ def fold(self, node): self.__class__._warned = True validate_call_args(node, 2) - values = [i._metadata.get("folded_value") for i in node.args] + values = [i.get_folded_value_maybe() for i in node.args] for val in values: if not isinstance(val, vy_ast.Int): raise UnfoldableNode @@ -1381,7 +1381,7 @@ def fold(self, node): self.__class__._warned = True validate_call_args(node, 2) - values = [i._metadata.get("folded_value") for i in node.args] + values = [i.get_folded_value_maybe() for i in node.args] for val in values: if not isinstance(val, vy_ast.Int): raise UnfoldableNode @@ -1406,7 +1406,7 @@ def fold(self, node): self.__class__._warned = True validate_call_args(node, 2) - values = [i._metadata.get("folded_value") for i in node.args] + values = [i.get_folded_value_maybe() for i in node.args] for val in values: if not isinstance(val, vy_ast.Int): raise UnfoldableNode @@ -1431,7 +1431,7 @@ def fold(self, node): self.__class__._warned = True validate_call_args(node, 1) - value = node.args[0]._metadata.get("folded_value") + value = node.args[0].get_folded_value_maybe() if not isinstance(value, vy_ast.Int): raise UnfoldableNode @@ -1457,7 +1457,7 @@ def fold(self, node): self.__class__._warned = True validate_call_args(node, 2) - args = [i._metadata.get("folded_value") for i in node.args] + args = [i.get_folded_value_maybe() for i in node.args] if any(not isinstance(i, vy_ast.Int) for i in args): raise UnfoldableNode value, shift = [i.value for i in args] @@ -1504,7 +1504,7 @@ class _AddMulMod(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 3) - args = [i._metadata.get("folded_value") for i in node.args] + args = [i.get_folded_value_maybe() for i in node.args] if isinstance(args[2], vy_ast.Int) and args[2].value == 0: raise ZeroDivisionException("Modulo by 0", node.args[2]) for arg in args: @@ -1545,7 +1545,7 @@ class PowMod256(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 2) - values = [i._metadata.get("folded_value") for i in node.args] + values = [i.get_folded_value_maybe() for i in node.args] if any(not isinstance(i, vy_ast.Int) for i in values): raise UnfoldableNode @@ -1566,7 +1566,7 @@ class Abs(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 1) - value = node.args[0]._metadata.get("folded_value") + value = node.args[0].get_folded_value_maybe() if not isinstance(value, vy_ast.Int): raise UnfoldableNode @@ -2006,8 +2006,8 @@ class _MinMax(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 2) - left = node.args[0]._metadata.get("folded_value") - right = node.args[1]._metadata.get("folded_value") + left = node.args[0].get_folded_value_maybe() + right = node.args[1].get_folded_value_maybe() if not isinstance(left, type(right)): raise UnfoldableNode if not isinstance(left, (vy_ast.Decimal, vy_ast.Int)): @@ -2083,7 +2083,7 @@ def fetch_call_return(self, node): def fold(self, node): validate_call_args(node, 1) - value = node.args[0]._metadata.get("folded_value") + value = node.args[0].get_folded_value_maybe() if not isinstance(value, vy_ast.Int): raise UnfoldableNode diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index c09dbcabf0..e8c651cce6 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -369,7 +369,7 @@ def visit_For(self, node): validate_expected_type(n, IntegerT.any()) if bound is None: - n_val = n._metadata.get("folded_value") + n_val = n.get_folded_value_maybe() if not isinstance(n_val, vy_ast.Num): raise StateAccessViolation("Value must be a literal", n) if n_val.value <= 0: @@ -377,7 +377,7 @@ def visit_For(self, node): type_list = get_possible_types_from_node(n) else: - bound_val = bound._metadata.get("folded_value") + bound_val = bound.get_folded_value_maybe() if not isinstance(bound_val, vy_ast.Num): raise StateAccessViolation("bound must be a literal", bound) if bound_val.value <= 0: @@ -394,7 +394,7 @@ def visit_For(self, node): validate_expected_type(args[0], IntegerT.any()) type_list = get_common_types(*args) - arg0_val = args[0]._metadata.get("folded_value") + arg0_val = args[0].get_folded_value_maybe() if not isinstance(arg0_val, vy_ast.Constant): # range(x, x + CONSTANT) if not isinstance(args[1], vy_ast.BinOp) or not isinstance( @@ -417,7 +417,7 @@ def visit_For(self, node): ) else: # range(CONSTANT, CONSTANT) - arg1_val = args[1]._metadata.get("folded_value") + arg1_val = args[1].get_folded_value_maybe() if not isinstance(arg1_val, vy_ast.Int): raise InvalidType("Value must be a literal integer", args[1]) validate_expected_type(args[1], IntegerT.any()) @@ -429,7 +429,7 @@ def visit_For(self, node): else: # iteration over a variable or literal list - iter_val = node.iter._metadata.get("folded_value") + iter_val = node.iter.get_folded_value_maybe() if isinstance(iter_val, vy_ast.List) and len(iter_val.elements) == 0: raise StructureException("For loop must have at least 1 iteration", node.iter) @@ -614,7 +614,7 @@ def visit(self, node, typ): # can happen. super().visit(node, typ) - folded_value = node._metadata.get("folded_value") + folded_value = node.get_folded_value_maybe() if isinstance(folded_value, vy_ast.Constant): validate_expected_type(folded_value, typ) diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index 203937c759..c2f77d79a0 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -21,7 +21,7 @@ def get_constants(node: vy_ast.Module) -> dict: for n in c.value.get_descendants(include_self=True, reverse=True): prefold(n, constants) - val = c.value._metadata.get("folded_value") + val = c.value.get_folded_value_maybe() # note that if a constant is redefined, its value will be overwritten, # but it is okay because the syntax error is handled downstream diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 2a53c0f62c..4230c82db8 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -634,7 +634,7 @@ def _check_literal(node: vy_ast.VyperNode) -> bool: elif isinstance(node, (vy_ast.Tuple, vy_ast.List)): return all(_check_literal(item) for item in node.elements) - if node._metadata.get("folded_value"): + if node.get_folded_value_maybe(): return True return False diff --git a/vyper/semantics/types/subscriptable.py b/vyper/semantics/types/subscriptable.py index 13f6d72297..5e1154416a 100644 --- a/vyper/semantics/types/subscriptable.py +++ b/vyper/semantics/types/subscriptable.py @@ -287,7 +287,7 @@ def from_annotation(cls, node: vy_ast.Subscript) -> "DArrayT": ): raise StructureException(err_msg, node.slice) - length_node = node.slice.value.elements[1]._metadata.get("folded_value") + length_node = node.slice.value.elements[1].get_folded_value_maybe() if not isinstance(length_node, vy_ast.Int): raise StructureException(err_msg, length_node) From 5bde191874c51e73b6e46e15267cbc08e60656b9 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Dec 2023 22:38:01 +0800 Subject: [PATCH 099/120] use get_folded_value_throwing for prefold --- vyper/semantics/analysis/pre_typecheck.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index c2f77d79a0..aa71113e7d 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -68,5 +68,9 @@ def prefold(node: vy_ast.VyperNode, constants: dict[str, vy_ast.VyperNode]): pass if getattr(node, "_is_prefoldable", None): - # call `get_folded_value_maybe` for its side effects - node.get_folded_value_maybe() + # call `get_folded_value_throwing` for its side effects and allow all + # exceptions other than `UnfoldableNode` to raise + try: + node.get_folded_value_throwing() + except UnfoldableNode: + pass From 6daf69fdfce313aa02412a41778ad01899dbdecd Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 25 Dec 2023 12:07:02 +0800 Subject: [PATCH 100/120] remove is_immutable; rename constancy to modifiability --- vyper/builtins/_signatures.py | 4 ++-- vyper/builtins/functions.py | 2 +- vyper/codegen/expr.py | 3 ++- vyper/semantics/analysis/base.py | 19 ++++++++----------- vyper/semantics/analysis/local.py | 10 +++++----- vyper/semantics/analysis/module.py | 9 ++++----- vyper/semantics/analysis/utils.py | 23 +++++++++++------------ vyper/semantics/environment.py | 2 +- vyper/semantics/types/function.py | 4 ++-- vyper/semantics/types/module.py | 4 ++-- 10 files changed, 38 insertions(+), 42 deletions(-) diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py index 9629510c79..7c8d396f3e 100644 --- a/vyper/builtins/_signatures.py +++ b/vyper/builtins/_signatures.py @@ -8,7 +8,7 @@ from vyper.exceptions import CompilerPanic, TypeMismatch, UnfoldableNode from vyper.semantics.analysis.base import Modifiability from vyper.semantics.analysis.utils import ( - check_variable_constancy, + check_modifiability, get_exact_type_from_node, validate_expected_type, ) @@ -111,7 +111,7 @@ def _validate_arg_types(self, node: vy_ast.Call) -> None: for kwarg in node.keywords: kwarg_settings = self._kwargs[kwarg.arg] - if kwarg_settings.require_literal and not check_variable_constancy( + if kwarg_settings.require_literal and not check_modifiability( kwarg.value, Modifiability.IMMUTABLE ): raise TypeMismatch("Value must be literal or environment variable", kwarg.value) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 9978670f71..c1f22a601a 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -106,7 +106,7 @@ class FoldedFunctionT(BuiltinFunctionT): # Base class for nodes which should always be folded # Since foldable builtin functions are not folded before semantics validation, - # this flag is used for `check_variable_constancy` in semantics validation. + # this flag is used for `check_modifiability` in semantics validation. _kwargable = True diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index 693d5c2aad..d1687bfc29 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -36,6 +36,7 @@ VyperException, tag_exceptions, ) +from vyper.semantics.analysis.base import Modifiability from vyper.semantics.types import ( AddressT, BoolT, @@ -186,7 +187,7 @@ def parse_Name(self): # TODO: use self.expr._expr_info elif self.expr.id in self.context.globals: varinfo = self.context.globals[self.expr.id] - assert varinfo.is_immutable, "not an immutable!" + assert varinfo.modifiability == Modifiability.IMMUTABLE, "not an immutable!" ofst = varinfo.position.offset diff --git a/vyper/semantics/analysis/base.py b/vyper/semantics/analysis/base.py index a34552adb7..5f17357a0f 100644 --- a/vyper/semantics/analysis/base.py +++ b/vyper/semantics/analysis/base.py @@ -190,7 +190,7 @@ class ImportInfo(AnalysisResult): class VarInfo: """ VarInfo are objects that represent the type of a variable, - plus associated metadata like location and constancy attributes + plus associated metadata like location and modifiability attributes Object Attributes ----------------- @@ -200,9 +200,8 @@ class VarInfo: typ: VyperType location: DataLocation = DataLocation.UNSET - constancy: Modifiability = Modifiability.MODIFIABLE + modifiability: Modifiability = Modifiability.MODIFIABLE is_public: bool = False - is_immutable: bool = False is_transient: bool = False is_local_var: bool = False decl_node: Optional[vy_ast.VyperNode] = None @@ -233,11 +232,10 @@ class ExprInfo: typ: VyperType var_info: Optional[VarInfo] = None location: DataLocation = DataLocation.UNSET - constancy: Modifiability = Modifiability.MODIFIABLE - is_immutable: bool = False + modifiability: Modifiability = Modifiability.MODIFIABLE def __post_init__(self): - should_match = ("typ", "location", "constancy", "is_immutable") + should_match = ("typ", "location", "modifiability") if self.var_info is not None: for attr in should_match: if getattr(self.var_info, attr) != getattr(self, attr): @@ -249,8 +247,7 @@ def from_varinfo(cls, var_info: VarInfo) -> "ExprInfo": var_info.typ, var_info=var_info, location=var_info.location, - constancy=var_info.constancy, - is_immutable=var_info.is_immutable, + modifiability=var_info.modifiability, ) @classmethod @@ -261,7 +258,7 @@ def copy_with_type(self, typ: VyperType) -> "ExprInfo": """ Return a copy of the ExprInfo but with the type set to something else """ - to_copy = ("location", "constancy", "is_immutable") + to_copy = ("location", "modifiability") fields = {k: getattr(self, k) for k in to_copy} return self.__class__(typ=typ, **fields) @@ -285,9 +282,9 @@ def validate_modification(self, node: vy_ast.VyperNode, mutability: StateMutabil if self.location == DataLocation.CALLDATA: raise ImmutableViolation("Cannot write to calldata", node) - if self.constancy == Modifiability.ALWAYS_CONSTANT: + if self.modifiability == Modifiability.ALWAYS_CONSTANT: raise ImmutableViolation("Constant value cannot be written to", node) - if self.is_immutable: + if self.modifiability == Modifiability.IMMUTABLE: if node.get_ancestor(vy_ast.FunctionDef).get("name") != "__init__": raise ImmutableViolation("Immutable value cannot be written to", node) # TODO: we probably want to remove this restriction. diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 68736d107e..0163547d55 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -190,14 +190,14 @@ def __init__( def analyze(self): # allow internal function params to be mutable - location, is_immutable, constancy = ( - (DataLocation.MEMORY, False, Modifiability.MODIFIABLE) + location, modifiability = ( + (DataLocation.MEMORY, Modifiability.MODIFIABLE) if self.func.is_internal - else (DataLocation.CALLDATA, True, Modifiability.NOT_MODIFIABLE) + else (DataLocation.CALLDATA, Modifiability.NOT_MODIFIABLE) ) for arg in self.func.arguments: self.namespace[arg.name] = VarInfo( - arg.typ, location=location, is_immutable=is_immutable, constancy=constancy + arg.typ, location=location, modifiability=modifiability ) for node in self.fn_node.body: @@ -425,7 +425,7 @@ def visit_For(self, node): with self.namespace.enter_scope(): self.namespace[iter_name] = VarInfo( - possible_target_type, constancy=Modifiability.ALWAYS_CONSTANT + possible_target_type, modifiability=Modifiability.ALWAYS_CONSTANT ) try: diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index d2fec61f98..ffbd2265db 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -26,7 +26,7 @@ from vyper.semantics.analysis.local import ExprVisitor, validate_functions from vyper.semantics.analysis.pre_typecheck import pre_typecheck from vyper.semantics.analysis.utils import ( - check_variable_constancy, + check_modifiability, get_exact_type_from_node, validate_expected_type, ) @@ -262,7 +262,7 @@ def visit_VariableDecl(self, node): else DataLocation.STORAGE ) - constancy = ( + modifiability = ( Modifiability.IMMUTABLE if node.is_immutable else Modifiability.ALWAYS_CONSTANT @@ -279,9 +279,8 @@ def visit_VariableDecl(self, node): type_, decl_node=node, location=data_loc, - constancy=constancy, + modifiability=modifiability, is_public=node.is_public, - is_immutable=node.is_immutable, is_transient=node.is_transient, ) node.target._metadata["varinfo"] = var_info # TODO maybe put this in the global namespace @@ -317,7 +316,7 @@ def _validate_self_namespace(): ExprVisitor().visit(node.value, type_) - if not check_variable_constancy(node.value, Modifiability.ALWAYS_CONSTANT): + if not check_modifiability(node.value, Modifiability.ALWAYS_CONSTANT): raise StateAccessViolation("Value must be a literal", node.value) validate_expected_type(node.value, type_) diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 4230c82db8..3f91d5f258 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -98,10 +98,9 @@ def get_expr_info(self, node: vy_ast.VyperNode) -> ExprInfo: # kludge! for validate_modification in local analysis of Assign types = [self.get_expr_info(n) for n in node.elements] location = sorted((i.location for i in types), key=lambda k: k.value)[-1] - constancy = sorted((i.constancy for i in types), key=lambda k: k.value)[-1] - is_immutable = any((getattr(i, "is_immutable", False) for i in types)) + modifiability = sorted((i.modifiability for i in types), key=lambda k: k.value)[-1] - return ExprInfo(t, location=location, constancy=constancy, is_immutable=is_immutable) + return ExprInfo(t, location=location, modifiability=modifiability) # If it's a Subscript, propagate the subscriptable varinfo if isinstance(node, vy_ast.Subscript): @@ -201,7 +200,7 @@ def _raise_invalid_reference(name, node): if isinstance(s, (VyperType, TYPE_T)): # ex. foo.bar(). bar() is a ContractFunctionT return [s] - if is_self_reference and s.constancy >= Modifiability.IMMUTABLE: + if is_self_reference and s.modifiability >= Modifiability.IMMUTABLE: _raise_invalid_reference(name, node) # general case. s is a VarInfo, e.g. self.foo return [s.typ] @@ -639,33 +638,33 @@ def _check_literal(node: vy_ast.VyperNode) -> bool: return False -def check_variable_constancy(node: vy_ast.VyperNode, constancy: Modifiability) -> bool: +def check_modifiability(node: vy_ast.VyperNode, modifiability: Modifiability) -> bool: """ - Check if the given node is a literal or constant value. + Check if the given node is not more modifiable than the given modifiability. """ if _check_literal(node): return True if isinstance(node, (vy_ast.BinOp, vy_ast.Compare)): - return all(check_variable_constancy(i, constancy) for i in (node.left, node.right)) + return all(check_modifiability(i, modifiability) for i in (node.left, node.right)) if isinstance(node, vy_ast.BoolOp): - return all(check_variable_constancy(i, constancy) for i in node.values) + return all(check_modifiability(i, modifiability) for i in node.values) if isinstance(node, vy_ast.UnaryOp): - return check_variable_constancy(node.operand, constancy) + return check_modifiability(node.operand, modifiability) if isinstance(node, (vy_ast.Tuple, vy_ast.List)): - return all(check_variable_constancy(item, constancy) for item in node.elements) + return all(check_modifiability(item, modifiability) for item in node.elements) if isinstance(node, vy_ast.Call): args = node.args if len(args) == 1 and isinstance(args[0], vy_ast.Dict): - return all(check_variable_constancy(v, constancy) for v in args[0].values) + return all(check_modifiability(v, modifiability) for v in args[0].values) call_type = get_exact_type_from_node(node.func) if getattr(call_type, "_kwargable", False): return True value_type = get_expr_info(node) - return value_type.constancy >= constancy + return value_type.modifiability >= modifiability diff --git a/vyper/semantics/environment.py b/vyper/semantics/environment.py index fb89d6dab7..295f40029e 100644 --- a/vyper/semantics/environment.py +++ b/vyper/semantics/environment.py @@ -52,7 +52,7 @@ def get_constant_vars() -> Dict: """ result = {} for k, v in CONSTANT_ENVIRONMENT_VARS.items(): - result[k] = VarInfo(v, constancy=Modifiability.CONSTANT_IN_CURRENT_TX) + result[k] = VarInfo(v, modifiability=Modifiability.CONSTANT_IN_CURRENT_TX) return result diff --git a/vyper/semantics/types/function.py b/vyper/semantics/types/function.py index c4c6aa2385..c71308e8fc 100644 --- a/vyper/semantics/types/function.py +++ b/vyper/semantics/types/function.py @@ -23,7 +23,7 @@ StorageSlot, ) from vyper.semantics.analysis.utils import ( - check_variable_constancy, + check_modifiability, get_exact_type_from_node, validate_expected_type, ) @@ -703,7 +703,7 @@ def _parse_args( positional_args.append(PositionalArg(argname, type_, ast_source=arg)) else: value = funcdef.args.defaults[i - n_positional_args] - if not check_variable_constancy(value, Modifiability.IMMUTABLE): + if not check_modifiability(value, Modifiability.IMMUTABLE): raise StateAccessViolation("Value must be literal or environment variable", value) validate_expected_type(value, type_) keyword_args.append(KeywordArg(argname, type_, value, ast_source=arg)) diff --git a/vyper/semantics/types/module.py b/vyper/semantics/types/module.py index b0d7800011..ccf9604bd4 100644 --- a/vyper/semantics/types/module.py +++ b/vyper/semantics/types/module.py @@ -5,7 +5,7 @@ from vyper.abi_types import ABI_Address, ABIType from vyper.ast.validation import validate_call_args from vyper.exceptions import InterfaceViolation, NamespaceCollision, StructureException -from vyper.semantics.analysis.base import VarInfo +from vyper.semantics.analysis.base import Modifiability, VarInfo from vyper.semantics.analysis.utils import validate_expected_type, validate_unique_method_ids from vyper.semantics.namespace import get_namespace from vyper.semantics.types.base import TYPE_T, VyperType @@ -324,7 +324,7 @@ def variables(self): @cached_property def immutables(self): - return [t for t in self.variables.values() if t.is_immutable] + return [t for t in self.variables.values() if t.modifiability == Modifiability.IMMUTABLE] @cached_property def immutable_section_bytes(self): From ade20fd14be6f23da4183d06ac51ae8c45f015b7 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 26 Dec 2023 16:35:25 +0800 Subject: [PATCH 101/120] use throwing instead of maybe when folding --- vyper/ast/nodes.py | 19 +++++++---------- vyper/builtins/functions.py | 42 ++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index e9d4482b01..e4e9f37866 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -917,11 +917,8 @@ class List(ExprNode): _translated_fields = {"elts": "elements"} def fold(self) -> Optional[ExprNode]: - elements = [e.get_folded_value_maybe() for e in self.elements] - if None not in elements: - return type(self).from_node(self, elements=elements) - - return None + elements = [e.get_folded_value_throwing() for e in self.elements] + return type(self).from_node(self, elements=elements) class Tuple(ExprNode): @@ -962,7 +959,7 @@ def fold(self) -> ExprNode: Int | Decimal Node representing the result of the evaluation. """ - operand = self.operand.get_folded_value_maybe() + operand = self.operand.get_folded_value_throwing() if isinstance(self.op, Not) and not isinstance(operand, NameConstant): raise UnfoldableNode("Node contains invalid field(s) for evaluation") @@ -1012,7 +1009,7 @@ def fold(self) -> ExprNode: Int | Decimal Node representing the result of the evaluation. """ - left, right = [i.get_folded_value_maybe() for i in (self.left, self.right)] + left, right = [i.get_folded_value_throwing() for i in (self.left, self.right)] if type(left) is not type(right): raise UnfoldableNode("Node contains invalid field(s) for evaluation") if not isinstance(left, (Int, Decimal)): @@ -1162,7 +1159,7 @@ def fold(self) -> ExprNode: NameConstant Node representing the result of the evaluation. """ - values = [i.get_folded_value_maybe() for i in self.values] + values = [i.get_folded_value_throwing() for i in self.values] if any(not isinstance(i, NameConstant) for i in values): raise UnfoldableNode("Node contains invalid field(s) for evaluation") @@ -1218,7 +1215,7 @@ def fold(self) -> ExprNode: NameConstant Node representing the result of the evaluation. """ - left, right = [i.get_folded_value_maybe() for i in (self.left, self.right)] + left, right = [i.get_folded_value_throwing() for i in (self.left, self.right)] if not isinstance(left, Constant): raise UnfoldableNode("Node contains invalid field(s) for evaluation") @@ -1324,8 +1321,8 @@ def fold(self) -> ExprNode: ExprNode Node representing the result of the evaluation. """ - slice_ = self.slice.value.get_folded_value_maybe() - value = self.value.get_folded_value_maybe() + slice_ = self.slice.value.get_folded_value_throwing() + value = self.value.get_folded_value_throwing() if not isinstance(value, List): raise UnfoldableNode("Subscript object is not a literal list") diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index c1f22a601a..718ad806c9 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -137,7 +137,7 @@ class Floor(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 1) - value = node.args[0].get_folded_value_maybe() + value = node.args[0].get_folded_value_throwing() if not isinstance(value, vy_ast.Decimal): raise UnfoldableNode @@ -168,7 +168,7 @@ class Ceil(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 1) - value = node.args[0].get_folded_value_maybe() + value = node.args[0].get_folded_value_throwing() if not isinstance(value, vy_ast.Decimal): raise UnfoldableNode @@ -462,7 +462,7 @@ class Len(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 1) - arg = node.args[0].get_folded_value_maybe() + arg = node.args[0].get_folded_value_throwing() if isinstance(arg, (vy_ast.Str, vy_ast.Bytes)): length = len(arg.value) elif isinstance(arg, vy_ast.Hex): @@ -599,7 +599,7 @@ class Keccak256(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 1) - value = node.args[0].get_folded_value_maybe() + value = node.args[0].get_folded_value_throwing() if isinstance(value, vy_ast.Bytes): value = value.value elif isinstance(value, vy_ast.Str): @@ -647,7 +647,7 @@ class Sha256(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 1) - value = node.args[0].get_folded_value_maybe() + value = node.args[0].get_folded_value_throwing() if isinstance(value, vy_ast.Bytes): value = value.value elif isinstance(value, vy_ast.Str): @@ -721,7 +721,7 @@ class MethodID(FoldedFunctionT): def fold(self, node): validate_call_args(node, 1, ["output_type"]) - value = node.args[0].get_folded_value_maybe() + value = node.args[0].get_folded_value_throwing() if not isinstance(value, vy_ast.Str): raise InvalidType("method id must be given as a literal string", node.args[0]) if " " in value.value: @@ -981,7 +981,7 @@ class AsWeiValue(BuiltinFunctionT): } def get_denomination(self, node): - value = node.args[1].get_folded_value_maybe() + value = node.args[1].get_folded_value_throwing() if not isinstance(value, vy_ast.Str): raise ArgumentException( "Wei denomination must be given as a literal string", node.args[1] @@ -997,7 +997,7 @@ def fold(self, node): validate_call_args(node, 2) denom = self.get_denomination(node) - value = node.args[0].get_folded_value_maybe() + value = node.args[0].get_folded_value_throwing() if not isinstance(value, (vy_ast.Decimal, vy_ast.Int)): raise UnfoldableNode value = value.value @@ -1083,10 +1083,10 @@ def fetch_call_return(self, node): outsize = kwargz.get("max_outsize") if outsize is not None: - outsize = outsize.get_folded_value_maybe() + outsize = outsize.get_folded_value_throwing() revert_on_failure = kwargz.get("revert_on_failure") if revert_on_failure is not None: - revert_on_failure = revert_on_failure.get_folded_value_maybe() + revert_on_failure = revert_on_failure.get_folded_value_throwing() revert_on_failure = revert_on_failure.value if revert_on_failure is not None else True if outsize is None or outsize.value == 0: @@ -1356,7 +1356,7 @@ def fold(self, node): self.__class__._warned = True validate_call_args(node, 2) - values = [i.get_folded_value_maybe() for i in node.args] + values = [i.get_folded_value_throwing() for i in node.args] for val in values: if not isinstance(val, vy_ast.Int): raise UnfoldableNode @@ -1381,7 +1381,7 @@ def fold(self, node): self.__class__._warned = True validate_call_args(node, 2) - values = [i.get_folded_value_maybe() for i in node.args] + values = [i.get_folded_value_throwing() for i in node.args] for val in values: if not isinstance(val, vy_ast.Int): raise UnfoldableNode @@ -1406,7 +1406,7 @@ def fold(self, node): self.__class__._warned = True validate_call_args(node, 2) - values = [i.get_folded_value_maybe() for i in node.args] + values = [i.get_folded_value_throwing() for i in node.args] for val in values: if not isinstance(val, vy_ast.Int): raise UnfoldableNode @@ -1431,7 +1431,7 @@ def fold(self, node): self.__class__._warned = True validate_call_args(node, 1) - value = node.args[0].get_folded_value_maybe() + value = node.args[0].get_folded_value_throwing() if not isinstance(value, vy_ast.Int): raise UnfoldableNode @@ -1457,7 +1457,7 @@ def fold(self, node): self.__class__._warned = True validate_call_args(node, 2) - args = [i.get_folded_value_maybe() for i in node.args] + args = [i.get_folded_value_throwing() for i in node.args] if any(not isinstance(i, vy_ast.Int) for i in args): raise UnfoldableNode value, shift = [i.value for i in args] @@ -1504,7 +1504,7 @@ class _AddMulMod(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 3) - args = [i.get_folded_value_maybe() for i in node.args] + args = [i.get_folded_value_throwing() for i in node.args] if isinstance(args[2], vy_ast.Int) and args[2].value == 0: raise ZeroDivisionException("Modulo by 0", node.args[2]) for arg in args: @@ -1545,7 +1545,7 @@ class PowMod256(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 2) - values = [i.get_folded_value_maybe() for i in node.args] + values = [i.get_folded_value_throwing() for i in node.args] if any(not isinstance(i, vy_ast.Int) for i in values): raise UnfoldableNode @@ -1566,7 +1566,7 @@ class Abs(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 1) - value = node.args[0].get_folded_value_maybe() + value = node.args[0].get_folded_value_throwing() if not isinstance(value, vy_ast.Int): raise UnfoldableNode @@ -2006,8 +2006,8 @@ class _MinMax(BuiltinFunctionT): def fold(self, node): validate_call_args(node, 2) - left = node.args[0].get_folded_value_maybe() - right = node.args[1].get_folded_value_maybe() + left = node.args[0].get_folded_value_throwing() + right = node.args[1].get_folded_value_throwing() if not isinstance(left, type(right)): raise UnfoldableNode if not isinstance(left, (vy_ast.Decimal, vy_ast.Int)): @@ -2083,7 +2083,7 @@ def fetch_call_return(self, node): def fold(self, node): validate_call_args(node, 1) - value = node.args[0].get_folded_value_maybe() + value = node.args[0].get_folded_value_throwing() if not isinstance(value, vy_ast.Int): raise UnfoldableNode From b0a295da565dc2f079943d51e20bfc7554476f62 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 26 Dec 2023 17:06:12 +0800 Subject: [PATCH 102/120] fix as wei value test --- tests/functional/syntax/test_as_wei_value.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/functional/syntax/test_as_wei_value.py b/tests/functional/syntax/test_as_wei_value.py index 4ed3f0a224..1a225a2bef 100644 --- a/tests/functional/syntax/test_as_wei_value.py +++ b/tests/functional/syntax/test_as_wei_value.py @@ -6,6 +6,7 @@ InvalidType, OverflowException, StructureException, + UndeclaredDefinition ) fail_list = [ @@ -15,7 +16,7 @@ def foo(): x: uint256 = as_wei_value(5, szabo) """, - ArgumentException, + UndeclaredDefinition, ), ( """ @@ -65,7 +66,7 @@ def foo(): """ FOO: constant(uint256) = as_wei_value(5, szabo) """, - ArgumentException, + UndeclaredDefinition, ), ( """ From 6838aa2655d9a92cf878e528cd525556a9d52cc7 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 26 Dec 2023 17:09:29 +0800 Subject: [PATCH 103/120] fix lint --- tests/functional/syntax/test_as_wei_value.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/syntax/test_as_wei_value.py b/tests/functional/syntax/test_as_wei_value.py index 1a225a2bef..dc72c55242 100644 --- a/tests/functional/syntax/test_as_wei_value.py +++ b/tests/functional/syntax/test_as_wei_value.py @@ -6,7 +6,7 @@ InvalidType, OverflowException, StructureException, - UndeclaredDefinition + UndeclaredDefinition, ) fail_list = [ From bce87d128b12261a37b048c5445d6962d852d7a1 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 26 Dec 2023 17:15:08 +0800 Subject: [PATCH 104/120] use throwing in prefold --- vyper/semantics/analysis/pre_typecheck.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index aa71113e7d..223407d839 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -21,14 +21,16 @@ def get_constants(node: vy_ast.Module) -> dict: for n in c.value.get_descendants(include_self=True, reverse=True): prefold(n, constants) - val = c.value.get_folded_value_maybe() + try: + val = c.value.get_folded_value_throwing() - # note that if a constant is redefined, its value will be overwritten, - # but it is okay because the syntax error is handled downstream - if val is not None: + # note that if a constant is redefined, its value will be overwritten, + # but it is okay because the syntax error is handled downstream constants[name] = val derived_nodes += 1 const_var_decls.remove(c) + except UnfoldableNode: + pass if not derived_nodes: break From e4fb5d80d386dca9c5fa2debc3793319c18c4e8d Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 26 Dec 2023 23:50:52 +0800 Subject: [PATCH 105/120] remove folding --- vyper/codegen/expr.py | 14 +++++++++++--- vyper/compiler/phases.py | 2 +- vyper/semantics/analysis/module.py | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index d1687bfc29..3b463a421e 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -70,6 +70,14 @@ class Expr: # TODO: Once other refactors are made reevaluate all inline imports def __init__(self, node, context): + # use original node for better diagnostics + orig_node = node + if isinstance(node, vy_ast.VyperNode): + folded_node = node._metadata.get("folded_value") + if folded_node: + folded_node._metadata["type"] = node._metadata["type"] + node = folded_node + self.expr = node self.context = context @@ -80,13 +88,13 @@ def __init__(self, node, context): fn = getattr(self, f"parse_{type(node).__name__}", None) if fn is None: - raise TypeCheckFailure(f"Invalid statement node: {type(node).__name__}", node) + raise TypeCheckFailure(f"Invalid statement node: {type(node).__name__}", orig_node) - with tag_exceptions(node, fallback_exception_type=CodegenPanic): + with tag_exceptions(orig_node, fallback_exception_type=CodegenPanic): self.ir_node = fn() if self.ir_node is None: - raise TypeCheckFailure(f"{type(node).__name__} node did not produce IR.\n", node) + raise TypeCheckFailure(f"{type(node).__name__} node did not produce IR.\n", orig_node) self.ir_node.annotation = self.expr.get("node_source_code") self.ir_node.source_pos = getpos(self.expr) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 4982e84b68..184c60c113 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -280,7 +280,7 @@ def generate_folded_ast( symbol_tables = set_data_positions(vyper_module, storage_layout_overrides) vyper_module_folded = copy.deepcopy(vyper_module) - vy_ast.folding.fold(vyper_module_folded) + #vy_ast.folding.fold(vyper_module_folded) return vyper_module_folded, symbol_tables diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index ffbd2265db..fa2ac73724 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -495,7 +495,7 @@ def _parse_and_fold_ast(file: FileInput) -> vy_ast.VyperNode: resolved_path=str(file.resolved_path), ) vy_ast.validation.validate_literal_nodes(ret) - vy_ast.folding.fold(ret) + #vy_ast.folding.fold(ret) return ret From 1f9be956a6608f926a5ee402605c630e38871568 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 27 Dec 2023 12:31:18 +0800 Subject: [PATCH 106/120] fix minmax folded node --- vyper/builtins/functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 718ad806c9..a924a56010 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -2020,7 +2020,7 @@ def fold(self, node): raise TypeMismatch("Cannot perform action between dislike numeric types", node) value = self._eval_fn(left.value, right.value) - return type(node.args[0]).from_node(node, value=value) + return type(left).from_node(node, value=value) def fetch_call_return(self, node): self._validate_arg_types(node) From 81d1bfb07cc3a68028275cbbb3bb1ca5ced5f66f Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 27 Dec 2023 12:31:23 +0800 Subject: [PATCH 107/120] improve tests --- tests/functional/syntax/test_abs.py | 4 ++++ tests/functional/syntax/test_as_wei_value.py | 4 ++++ tests/functional/syntax/test_ceil.py | 1 + tests/functional/syntax/test_floor.py | 1 + tests/functional/syntax/test_len.py | 1 + tests/functional/syntax/test_minmax.py | 8 ++++++++ tests/functional/syntax/test_minmax_value.py | 4 ++++ tests/functional/syntax/test_powmod.py | 4 ++++ tests/functional/syntax/test_uint2str.py | 4 ++++ 9 files changed, 31 insertions(+) diff --git a/tests/functional/syntax/test_abs.py b/tests/functional/syntax/test_abs.py index cb231a33b1..28ac8bc769 100644 --- a/tests/functional/syntax/test_abs.py +++ b/tests/functional/syntax/test_abs.py @@ -26,6 +26,10 @@ def test_abs_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_c """ FOO: constant(int256) = -3 BAR: constant(int256) = abs(FOO) + +@external +def foo(): + a: int256 = BAR """ ] diff --git a/tests/functional/syntax/test_as_wei_value.py b/tests/functional/syntax/test_as_wei_value.py index dc72c55242..6ec01a3214 100644 --- a/tests/functional/syntax/test_as_wei_value.py +++ b/tests/functional/syntax/test_as_wei_value.py @@ -114,6 +114,10 @@ def foo() -> uint256: """ y: constant(String[5]) = "szabo" x: constant(uint256) = as_wei_value(5, y) + +@external +def foo(): + a: uint256 = x """, ] diff --git a/tests/functional/syntax/test_ceil.py b/tests/functional/syntax/test_ceil.py index 53608ebea4..f9a4f3bbe2 100644 --- a/tests/functional/syntax/test_ceil.py +++ b/tests/functional/syntax/test_ceil.py @@ -6,6 +6,7 @@ """ BAR: constant(decimal) = 2.5 FOO: constant(int256) = ceil(BAR) + @external def foo(): a: int256 = FOO diff --git a/tests/functional/syntax/test_floor.py b/tests/functional/syntax/test_floor.py index 7b7f960a86..6f3abf1dd0 100644 --- a/tests/functional/syntax/test_floor.py +++ b/tests/functional/syntax/test_floor.py @@ -6,6 +6,7 @@ """ BAR: constant(decimal) = 2.5 FOO: constant(int256) = floor(BAR) + @external def foo(): a: int256 = FOO diff --git a/tests/functional/syntax/test_len.py b/tests/functional/syntax/test_len.py index 6867ceda63..449e89fe56 100644 --- a/tests/functional/syntax/test_len.py +++ b/tests/functional/syntax/test_len.py @@ -42,6 +42,7 @@ def foo(inp: String[10]) -> uint256: """ BAR: constant(String[5]) = "vyper" FOO: constant(uint256) = len(BAR) + @external def foo() -> uint256: a: uint256 = FOO diff --git a/tests/functional/syntax/test_minmax.py b/tests/functional/syntax/test_minmax.py index 6565974f19..c1129f7251 100644 --- a/tests/functional/syntax/test_minmax.py +++ b/tests/functional/syntax/test_minmax.py @@ -41,11 +41,19 @@ def test_block_fail(assert_compile_failed, get_contract_with_gas_estimation, bad FOO: constant(uint256) = 123 BAR: constant(uint256) = 456 BAZ: constant(uint256) = min(FOO, BAR) + +@external +def foo(): + a: uint256 = BAZ """, """ FOO: constant(uint256) = 123 BAR: constant(uint256) = 456 BAZ: constant(uint256) = max(FOO, BAR) + +@external +def foo(): + a: uint256 = BAZ """, ] diff --git a/tests/functional/syntax/test_minmax_value.py b/tests/functional/syntax/test_minmax_value.py index 14b61e2f0a..4249d8419b 100644 --- a/tests/functional/syntax/test_minmax_value.py +++ b/tests/functional/syntax/test_minmax_value.py @@ -15,6 +15,10 @@ def foo(): """, """ FOO: constant(address) = min_value(address) + +@external +def foo(): + a: address = FOO """, ] diff --git a/tests/functional/syntax/test_powmod.py b/tests/functional/syntax/test_powmod.py index fce692a74a..a86039ca4a 100644 --- a/tests/functional/syntax/test_powmod.py +++ b/tests/functional/syntax/test_powmod.py @@ -25,6 +25,10 @@ def test_powmod_fail(assert_compile_failed, get_contract_with_gas_estimation, ba FOO: constant(uint256) = 3 BAR: constant(uint256) = 5 BAZ: constant(uint256) = pow_mod256(FOO, BAR) + +@external +def foo(): + a: uint256 = BAZ """ ] diff --git a/tests/functional/syntax/test_uint2str.py b/tests/functional/syntax/test_uint2str.py index 392b7b952b..90e929421c 100644 --- a/tests/functional/syntax/test_uint2str.py +++ b/tests/functional/syntax/test_uint2str.py @@ -6,6 +6,10 @@ """ FOO: constant(uint256) = 3 BAR: constant(String[78]) = uint2str(FOO) + +@external +def foo(): + a: String[78] = BAR """ ] From 3736bf525b46b8d8d9bcc22abff5e778026afb04 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 27 Dec 2023 12:43:27 +0800 Subject: [PATCH 108/120] use pytest.raises instead of assert_compile_failed --- .../exceptions/test_argument_exception.py | 4 ++-- tests/functional/syntax/test_abs.py | 9 ++++---- tests/functional/syntax/test_addmulmod.py | 4 ++-- tests/functional/syntax/test_as_wei_value.py | 6 +++-- tests/functional/syntax/test_ceil.py | 2 +- tests/functional/syntax/test_dynamic_array.py | 9 ++++---- tests/functional/syntax/test_epsilon.py | 15 ++++++++---- tests/functional/syntax/test_floor.py | 2 +- tests/functional/syntax/test_len.py | 13 +++++------ tests/functional/syntax/test_method_id.py | 9 ++++---- tests/functional/syntax/test_minmax.py | 9 ++++---- tests/functional/syntax/test_minmax_value.py | 23 ++++++++++++++----- tests/functional/syntax/test_powmod.py | 9 ++++---- tests/functional/syntax/test_raw_call.py | 6 ++--- tests/functional/syntax/test_ternary.py | 2 +- tests/functional/syntax/test_uint2str.py | 4 ++-- tests/functional/syntax/test_unary.py | 2 +- 17 files changed, 75 insertions(+), 53 deletions(-) diff --git a/tests/functional/syntax/exceptions/test_argument_exception.py b/tests/functional/syntax/exceptions/test_argument_exception.py index 939cc7104b..0b7ec21bdb 100644 --- a/tests/functional/syntax/exceptions/test_argument_exception.py +++ b/tests/functional/syntax/exceptions/test_argument_exception.py @@ -1,6 +1,6 @@ import pytest -from vyper import compiler +from vyper import compile_code from vyper.exceptions import ArgumentException fail_list = [ @@ -95,4 +95,4 @@ def foo(): @pytest.mark.parametrize("bad_code", fail_list) def test_function_declaration_exception(bad_code): with pytest.raises(ArgumentException): - compiler.compile_code(bad_code) + compile_code(bad_code) diff --git a/tests/functional/syntax/test_abs.py b/tests/functional/syntax/test_abs.py index 28ac8bc769..0841ff05d6 100644 --- a/tests/functional/syntax/test_abs.py +++ b/tests/functional/syntax/test_abs.py @@ -1,6 +1,6 @@ import pytest -from vyper import compiler +from vyper import compile_code from vyper.exceptions import InvalidType fail_list = [ @@ -18,8 +18,9 @@ def foo(): @pytest.mark.parametrize("bad_code,exc", fail_list) -def test_abs_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_code, exc): - assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code), exc) +def test_abs_fail(bad_code, exc): + with pytest.raises(exc): + compile_code(bad_code) valid_list = [ @@ -36,4 +37,4 @@ def foo(): @pytest.mark.parametrize("code", valid_list) def test_abs_pass(code): - assert compiler.compile_code(code) is not None + assert compile_code(code) is not None diff --git a/tests/functional/syntax/test_addmulmod.py b/tests/functional/syntax/test_addmulmod.py index 7be87ddc97..17c7b3ab8c 100644 --- a/tests/functional/syntax/test_addmulmod.py +++ b/tests/functional/syntax/test_addmulmod.py @@ -1,6 +1,6 @@ import pytest -from vyper import compiler +from vyper import compile_code from vyper.exceptions import InvalidType fail_list = [ @@ -46,4 +46,4 @@ def test_add_mod_fail(assert_compile_failed, get_contract, code, exc): @pytest.mark.parametrize("code", valid_list) def test_addmulmod_pass(code): - assert compiler.compile_code(code) is not None + assert compile_code(code) is not None diff --git a/tests/functional/syntax/test_as_wei_value.py b/tests/functional/syntax/test_as_wei_value.py index 6ec01a3214..a68f30424e 100644 --- a/tests/functional/syntax/test_as_wei_value.py +++ b/tests/functional/syntax/test_as_wei_value.py @@ -1,5 +1,6 @@ import pytest +from vyper import compile_code from vyper.exceptions import ( ArgumentException, InvalidLiteral, @@ -84,8 +85,9 @@ def foo(): @pytest.mark.parametrize("bad_code,exc", fail_list) -def test_as_wei_fail(get_contract_with_gas_estimation, bad_code, exc, assert_compile_failed): - assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code), exc) +def test_as_wei_fail(bad_code, exc): + with pytest.raises(exc): + compile_code(bad_code) valid_list = [ diff --git a/tests/functional/syntax/test_ceil.py b/tests/functional/syntax/test_ceil.py index f9a4f3bbe2..41f4175d01 100644 --- a/tests/functional/syntax/test_ceil.py +++ b/tests/functional/syntax/test_ceil.py @@ -1,6 +1,6 @@ import pytest -from vyper.compiler import compile_code +from vyper import compile_code valid_list = [ """ diff --git a/tests/functional/syntax/test_dynamic_array.py b/tests/functional/syntax/test_dynamic_array.py index ae4a55c124..f566a80625 100644 --- a/tests/functional/syntax/test_dynamic_array.py +++ b/tests/functional/syntax/test_dynamic_array.py @@ -1,6 +1,6 @@ import pytest -from vyper import compiler +from vyper import compile_code from vyper.exceptions import StructureException fail_list = [ @@ -36,8 +36,9 @@ def foo(): @pytest.mark.parametrize("bad_code,exc", fail_list) -def test_block_fail(assert_compile_failed, get_contract, bad_code, exc): - assert_compile_failed(lambda: get_contract(bad_code), exc) +def test_block_fail(bad_code, exc): + with pytest.raises(exc): + compile_code(bad_code) valid_list = [ @@ -56,4 +57,4 @@ def test_block_fail(assert_compile_failed, get_contract, bad_code, exc): @pytest.mark.parametrize("good_code", valid_list) def test_dynarray_pass(good_code): - assert compiler.compile_code(good_code) is not None + assert compile_code(good_code) is not None diff --git a/tests/functional/syntax/test_epsilon.py b/tests/functional/syntax/test_epsilon.py index 73369d4b8a..8fa5bc70ca 100644 --- a/tests/functional/syntax/test_epsilon.py +++ b/tests/functional/syntax/test_epsilon.py @@ -1,14 +1,19 @@ import pytest +from vyper import compile_code from vyper.exceptions import InvalidType fail_list = [ - """ + ( + """ FOO: constant(address) = epsilon(address) - """ + """, + InvalidType, + ) ] -@pytest.mark.parametrize("bad_code", fail_list) -def test_block_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_code): - assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code), InvalidType) +@pytest.mark.parametrize("bad_code,exc", fail_list) +def test_block_fail(bad_code, exc): + with pytest.raises(exc): + compile_code(bad_code) diff --git a/tests/functional/syntax/test_floor.py b/tests/functional/syntax/test_floor.py index 6f3abf1dd0..5c30aecbe1 100644 --- a/tests/functional/syntax/test_floor.py +++ b/tests/functional/syntax/test_floor.py @@ -1,6 +1,6 @@ import pytest -from vyper.compiler import compile_code +from vyper import compile_code valid_list = [ """ diff --git a/tests/functional/syntax/test_len.py b/tests/functional/syntax/test_len.py index 449e89fe56..b8cc61df1d 100644 --- a/tests/functional/syntax/test_len.py +++ b/tests/functional/syntax/test_len.py @@ -1,7 +1,6 @@ import pytest -from pytest import raises -from vyper import compiler +from vyper import compile_code from vyper.exceptions import TypeMismatch fail_list = [ @@ -21,11 +20,11 @@ def foo(inp: int128) -> uint256: @pytest.mark.parametrize("bad_code", fail_list) def test_block_fail(bad_code): if isinstance(bad_code, tuple): - with raises(bad_code[1]): - compiler.compile_code(bad_code[0]) + with pytest.raises(bad_code[1]): + compile_code(bad_code[0]) else: - with raises(TypeMismatch): - compiler.compile_code(bad_code) + with pytest.raises(TypeMismatch): + compile_code(bad_code) valid_list = [ @@ -53,4 +52,4 @@ def foo() -> uint256: @pytest.mark.parametrize("good_code", valid_list) def test_list_success(good_code): - assert compiler.compile_code(good_code) is not None + assert compile_code(good_code) is not None diff --git a/tests/functional/syntax/test_method_id.py b/tests/functional/syntax/test_method_id.py index da67e67fe3..849c1b0d55 100644 --- a/tests/functional/syntax/test_method_id.py +++ b/tests/functional/syntax/test_method_id.py @@ -1,6 +1,6 @@ import pytest -from vyper import compiler +from vyper import compile_code from vyper.exceptions import InvalidLiteral, InvalidType fail_list = [ @@ -28,8 +28,9 @@ def foo(): @pytest.mark.parametrize("bad_code,exc", fail_list) -def test_method_id_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_code, exc): - assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code), exc) +def test_method_id_fail(bad_code, exc): + with pytest.raises(exc): + compile_code(bad_code) valid_list = [ @@ -46,4 +47,4 @@ def foo(a: Bytes[4] = BAR): @pytest.mark.parametrize("code", valid_list) def test_method_id_pass(code): - assert compiler.compile_code(code) is not None + assert compile_code(code) is not None diff --git a/tests/functional/syntax/test_minmax.py b/tests/functional/syntax/test_minmax.py index c1129f7251..78ee74635c 100644 --- a/tests/functional/syntax/test_minmax.py +++ b/tests/functional/syntax/test_minmax.py @@ -1,6 +1,6 @@ import pytest -from vyper import compiler +from vyper import compile_code from vyper.exceptions import InvalidType, OverflowException, TypeMismatch fail_list = [ @@ -32,8 +32,9 @@ def foo(): @pytest.mark.parametrize("bad_code,exc", fail_list) -def test_block_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_code, exc): - assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code), exc) +def test_block_fail(bad_code, exc): + with pytest.raises(exc): + compile_code(bad_code) valid_list = [ @@ -60,4 +61,4 @@ def foo(): @pytest.mark.parametrize("good_code", valid_list) def test_block_success(good_code): - assert compiler.compile_code(good_code) is not None + assert compile_code(good_code) is not None diff --git a/tests/functional/syntax/test_minmax_value.py b/tests/functional/syntax/test_minmax_value.py index 4249d8419b..8cc3370b42 100644 --- a/tests/functional/syntax/test_minmax_value.py +++ b/tests/functional/syntax/test_minmax_value.py @@ -1,28 +1,39 @@ import pytest +from vyper import compile_code from vyper.exceptions import InvalidType fail_list = [ - """ + ( + """ @external def foo(): a: address = min_value(address) """, - """ + InvalidType, + ), + ( + """ @external def foo(): a: address = max_value(address) """, - """ + InvalidType, + ), + ( + """ FOO: constant(address) = min_value(address) @external def foo(): a: address = FOO """, + InvalidType, + ), ] -@pytest.mark.parametrize("bad_code", fail_list) -def test_block_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_code): - assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code), InvalidType) +@pytest.mark.parametrize("bad_code,exc", fail_list) +def test_block_fail(bad_code, exc): + with pytest.raises(exc): + compile_code(bad_code) diff --git a/tests/functional/syntax/test_powmod.py b/tests/functional/syntax/test_powmod.py index a86039ca4a..12ea23152c 100644 --- a/tests/functional/syntax/test_powmod.py +++ b/tests/functional/syntax/test_powmod.py @@ -1,6 +1,6 @@ import pytest -from vyper import compiler +from vyper import compile_code from vyper.exceptions import InvalidType fail_list = [ @@ -16,8 +16,9 @@ def foo(): @pytest.mark.parametrize("bad_code,exc", fail_list) -def test_powmod_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_code, exc): - assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code), exc) +def test_powmod_fail(bad_code, exc): + with pytest.raises(exc): + compile_code(bad_code) valid_list = [ @@ -35,4 +36,4 @@ def foo(): @pytest.mark.parametrize("code", valid_list) def test_powmod_pass(code): - assert compiler.compile_code(code) is not None + assert compile_code(code) is not None diff --git a/tests/functional/syntax/test_raw_call.py b/tests/functional/syntax/test_raw_call.py index 4ff058ba40..c0b38d1d1e 100644 --- a/tests/functional/syntax/test_raw_call.py +++ b/tests/functional/syntax/test_raw_call.py @@ -1,6 +1,6 @@ import pytest -from vyper import compiler +from vyper import compile_code from vyper.exceptions import ArgumentException, InvalidType, SyntaxException, TypeMismatch fail_list = [ @@ -39,7 +39,7 @@ def foo(): @pytest.mark.parametrize("bad_code,exc", fail_list) def test_raw_call_fail(bad_code, exc): with pytest.raises(exc): - compiler.compile_code(bad_code) + compile_code(bad_code) valid_list = [ @@ -109,4 +109,4 @@ def foo(): @pytest.mark.parametrize("good_code", valid_list) def test_raw_call_success(good_code): - assert compiler.compile_code(good_code) is not None + assert compile_code(good_code) is not None diff --git a/tests/functional/syntax/test_ternary.py b/tests/functional/syntax/test_ternary.py index 3573648368..6a2bb9c072 100644 --- a/tests/functional/syntax/test_ternary.py +++ b/tests/functional/syntax/test_ternary.py @@ -1,6 +1,6 @@ import pytest -from vyper.compiler import compile_code +from vyper import compile_code from vyper.exceptions import InvalidType, TypeMismatch good_list = [ diff --git a/tests/functional/syntax/test_uint2str.py b/tests/functional/syntax/test_uint2str.py index 90e929421c..9e6dde30cc 100644 --- a/tests/functional/syntax/test_uint2str.py +++ b/tests/functional/syntax/test_uint2str.py @@ -1,6 +1,6 @@ import pytest -from vyper import compiler +from vyper import compile_code valid_list = [ """ @@ -16,4 +16,4 @@ def foo(): @pytest.mark.parametrize("code", valid_list) def test_addmulmod_pass(code): - assert compiler.compile_code(code) is not None + assert compile_code(code) is not None diff --git a/tests/functional/syntax/test_unary.py b/tests/functional/syntax/test_unary.py index c19e5ba5f0..5942ee15db 100644 --- a/tests/functional/syntax/test_unary.py +++ b/tests/functional/syntax/test_unary.py @@ -1,6 +1,6 @@ import pytest -from vyper.compiler import compile_code +from vyper import compile_code from vyper.exceptions import InvalidType fail_list = [ From e1e557622bb771e6284eca5b032726eaccec38ce Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 27 Dec 2023 13:09:55 +0800 Subject: [PATCH 109/120] fix kwarg handling --- vyper/builtins/_signatures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py index 7c8d396f3e..f955296ee0 100644 --- a/vyper/builtins/_signatures.py +++ b/vyper/builtins/_signatures.py @@ -34,7 +34,7 @@ def process_arg(arg, expected_arg_type, context): def process_kwarg(kwarg_node, kwarg_settings, expected_kwarg_type, context): if kwarg_settings.require_literal: - return kwarg_node.value + return kwarg_node.get_folded_value_throwing().value return process_arg(kwarg_node, expected_kwarg_type, context) From 3a642fbb1179f20e4bc3b110e5beb008c71ec88e Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 27 Dec 2023 15:12:39 +0800 Subject: [PATCH 110/120] fix kwargs and constant structs --- vyper/codegen/expr.py | 7 ++++ .../function_definitions/external_function.py | 40 +++++++++---------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index d42ba8babd..25927f1ab0 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -193,6 +193,13 @@ def parse_Name(self): # TODO: use self.expr._expr_info elif self.expr.id in self.context.globals: varinfo = self.context.globals[self.expr.id] + if varinfo.modifiability == Modifiability.ALWAYS_CONSTANT: + value_node = varinfo.decl_node.value + folded_value_node = value_node._metadata.get("folded_value") + if folded_value_node is not None: + value_node = folded_value_node + return Expr.parse_value_expr(value_node, self.context) + assert varinfo.modifiability == Modifiability.IMMUTABLE, "not an immutable!" ofst = varinfo.position.offset diff --git a/vyper/codegen/function_definitions/external_function.py b/vyper/codegen/function_definitions/external_function.py index f46aa6b2e5..f1a6a1ee1d 100644 --- a/vyper/codegen/function_definitions/external_function.py +++ b/vyper/codegen/function_definitions/external_function.py @@ -8,7 +8,7 @@ from vyper.codegen.stmt import parse_body from vyper.evm.address_space import CALLDATA, DATA, MEMORY from vyper.semantics.types import TupleT -from vyper.semantics.types.function import ContractFunctionT +from vyper.semantics.types.function import ContractFunctionT, KeywordArg, PositionalArg # register function args with the local calling context. @@ -63,12 +63,14 @@ def _generate_kwarg_handlers( # write default args to memory # goto external_function_common_ir - def handler_for(calldata_kwargs, original_default_kwargs, folded_default_kwargs): - calldata_args = func_t.positional_args + calldata_kwargs + def handler_for(calldata_kwargs_info, default_kwargs_info, default_kwargs): + calldata_args_info: list[Union[PositionalArg, KeywordArg]] = ( + func_t.positional_args + calldata_kwargs_info + ) # create a fake type so that get_element_ptr works - calldata_args_t = TupleT(list(arg.typ for arg in calldata_args)) + calldata_args_t = TupleT(list(arg.typ for arg in calldata_args_info)) - abi_sig = func_t.abi_signature_for_kwargs(calldata_kwargs) + abi_sig = func_t.abi_signature_for_kwargs(calldata_kwargs_info) calldata_kwargs_ofst = IRnode( 4, location=CALLDATA, typ=calldata_args_t, encoding=Encoding.ABI @@ -82,10 +84,10 @@ def handler_for(calldata_kwargs, original_default_kwargs, folded_default_kwargs) calldata_min_size = args_abi_t.min_size() + 4 # TODO optimize make_setter by using - # TupleT(list(arg.typ for arg in calldata_kwargs + folded_default_kwargs)) + # TupleT(list(arg.typ for arg in calldata_kwargs_info + default_kwargs_info)) # (must ensure memory area is contiguous) - for i, arg_meta in enumerate(calldata_kwargs): + for i, arg_meta in enumerate(calldata_kwargs_info): k = func_t.n_positional_args + i dst = context.lookup_var(arg_meta.name).pos @@ -98,7 +100,7 @@ def handler_for(calldata_kwargs, original_default_kwargs, folded_default_kwargs) copy_arg.source_pos = getpos(arg_meta.ast_source) ret.append(copy_arg) - for x, y in zip(original_default_kwargs, folded_default_kwargs): + for x, y in zip(default_kwargs_info, default_kwargs): dst = context.lookup_var(x.name).pos lhs = IRnode(dst, location=MEMORY, typ=x.typ) lhs.source_pos = getpos(y) @@ -116,26 +118,24 @@ def handler_for(calldata_kwargs, original_default_kwargs, folded_default_kwargs) ret = {} - keyword_args = func_t.keyword_args - folded_keyword_args = code.args.defaults + keyword_args_info = func_t.keyword_args + keyword_args = code.args.defaults - # allocate variable slots in memory - for arg in keyword_args: + # allocate keyword_args_info slots in memory + for arg in keyword_args_info: context.new_variable(arg.name, arg.typ, is_mutable=False) - for i, _ in enumerate(keyword_args): - calldata_kwargs = keyword_args[:i] - # folded ast - original_default_kwargs = keyword_args[i:] - # unfolded ast - folded_default_kwargs = folded_keyword_args[i:] + for i, _ in enumerate(keyword_args_info): + calldata_kwargs_info = keyword_args_info[:i] + default_kwargs_info: list[KeywordArg] = keyword_args_info[i:] + default_kwargs: list[vy_ast.VyperNode] = keyword_args[i:] sig, calldata_min_size, ir_node = handler_for( - calldata_kwargs, original_default_kwargs, folded_default_kwargs + calldata_kwargs_info, default_kwargs_info, default_kwargs ) ret[sig] = calldata_min_size, ir_node - sig, calldata_min_size, ir_node = handler_for(keyword_args, [], []) + sig, calldata_min_size, ir_node = handler_for(keyword_args_info, [], []) ret[sig] = calldata_min_size, ir_node From 27441ec27cff496a67001fb8f6b6e72fdd2e6170 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 27 Dec 2023 16:46:43 +0800 Subject: [PATCH 111/120] fix list codegen --- vyper/codegen/expr.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index 25927f1ab0..28de2a18be 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -720,6 +720,9 @@ def parse_List(self): if len(self.expr.elements) == 0: return IRnode.from_list("~empty", typ=typ) + for e in self.expr.elements: + if "type" not in e._metadata: + e._metadata["type"] = typ.subtype multi_ir = [Expr(x, self.context).ir_node for x in self.expr.elements] return IRnode.from_list(["multi"] + multi_ir, typ=typ) From 394c9fbf2c73e2a8d4603f3cd3ad7436a912c189 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 27 Dec 2023 17:21:57 +0800 Subject: [PATCH 112/120] uncatch vyper exception when prefolding call nodes --- vyper/semantics/analysis/pre_typecheck.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/semantics/analysis/pre_typecheck.py b/vyper/semantics/analysis/pre_typecheck.py index 223407d839..b89c1c6759 100644 --- a/vyper/semantics/analysis/pre_typecheck.py +++ b/vyper/semantics/analysis/pre_typecheck.py @@ -1,5 +1,5 @@ from vyper import ast as vy_ast -from vyper.exceptions import UnfoldableNode, VyperException +from vyper.exceptions import UnfoldableNode def get_constants(node: vy_ast.Module) -> dict: @@ -66,7 +66,7 @@ def prefold(node: vy_ast.VyperNode, constants: dict[str, vy_ast.VyperNode]): try: node._metadata["folded_value"] = call_type.fold(node) return - except (UnfoldableNode, VyperException): + except UnfoldableNode: pass if getattr(node, "_is_prefoldable", None): From 17f01fafcb0089acae2325569d73692a265c1cd8 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 28 Dec 2023 10:45:30 +0800 Subject: [PATCH 113/120] handle const, list and tuples --- vyper/ast/nodes.py | 47 ++++++++++++++++++++++++++++++- vyper/codegen/expr.py | 3 -- vyper/semantics/analysis/utils.py | 16 +---------- 3 files changed, 47 insertions(+), 19 deletions(-) diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index e4e9f37866..cc4929286e 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -777,7 +777,12 @@ class Constant(ExprNode): def __init__(self, parent: Optional["VyperNode"] = None, **kwargs: dict): super().__init__(parent, **kwargs) - self._metadata["folded_value"] = self + + def get_folded_value_throwing(self) -> "VyperNode": + return self + + def get_folded_value_maybe(self) -> Optional["VyperNode"]: + return self class Num(Constant): @@ -911,6 +916,18 @@ def s(self): return self.value +def check_literal(node: VyperNode) -> bool: + """ + Check if the given node is a literal value. + """ + if isinstance(node, Constant): + return True + elif isinstance(node, (Tuple, List)): + return all(check_literal(item) for item in node.elements) + + return False + + class List(ExprNode): __slots__ = ("elements",) _is_prefoldable = True @@ -920,6 +937,18 @@ def fold(self) -> Optional[ExprNode]: elements = [e.get_folded_value_throwing() for e in self.elements] return type(self).from_node(self, elements=elements) + def get_folded_value_throwing(self) -> "VyperNode": + if check_literal(self): + return self + + return super().get_folded_value_throwing() + + def get_folded_value_maybe(self) -> Optional["VyperNode"]: + if check_literal(self): + return self + + return super().get_folded_value_maybe() + class Tuple(ExprNode): __slots__ = ("elements",) @@ -929,6 +958,22 @@ def validate(self): if not self.elements: raise InvalidLiteral("Cannot have an empty tuple", self) + def fold(self) -> Optional[ExprNode]: + elements = [e.get_folded_value_throwing() for e in self.elements] + return type(self).from_node(self, elements=elements) + + def get_folded_value_throwing(self) -> "VyperNode": + if check_literal(self): + return self + + return super().get_folded_value_throwing() + + def get_folded_value_maybe(self) -> Optional["VyperNode"]: + if check_literal(self): + return self + + return super().get_folded_value_maybe() + class NameConstant(Constant): __slots__ = () diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index 28de2a18be..25927f1ab0 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -720,9 +720,6 @@ def parse_List(self): if len(self.expr.elements) == 0: return IRnode.from_list("~empty", typ=typ) - for e in self.expr.elements: - if "type" not in e._metadata: - e._metadata["type"] = typ.subtype multi_ir = [Expr(x, self.context).ir_node for x in self.expr.elements] return IRnode.from_list(["multi"] + multi_ir, typ=typ) diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 3f91d5f258..dcf81b4d6e 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -624,25 +624,11 @@ def validate_unique_method_ids(functions: List) -> None: seen.add(method_id) -def _check_literal(node: vy_ast.VyperNode) -> bool: - """ - Check if the given node is a literal value. - """ - if isinstance(node, vy_ast.Constant): - return True - elif isinstance(node, (vy_ast.Tuple, vy_ast.List)): - return all(_check_literal(item) for item in node.elements) - - if node.get_folded_value_maybe(): - return True - return False - - def check_modifiability(node: vy_ast.VyperNode, modifiability: Modifiability) -> bool: """ Check if the given node is not more modifiable than the given modifiability. """ - if _check_literal(node): + if node.get_folded_value_maybe(): return True if isinstance(node, (vy_ast.BinOp, vy_ast.Compare)): From 93a9b4218cd4db82d0ebce0057270a081c913ff3 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 28 Dec 2023 14:56:01 +0800 Subject: [PATCH 114/120] fix tuple --- vyper/ast/nodes.py | 1 + vyper/semantics/analysis/local.py | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index cc4929286e..25da0714ee 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -952,6 +952,7 @@ def get_folded_value_maybe(self) -> Optional["VyperNode"]: class Tuple(ExprNode): __slots__ = ("elements",) + _is_prefoldable = True _translated_fields = {"elts": "elements"} def validate(self): diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 0163547d55..57a9e9afa9 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -545,13 +545,15 @@ def visit(self, node, typ): # can happen. super().visit(node, typ) - folded_value = node.get_folded_value_maybe() - if isinstance(folded_value, vy_ast.Constant): - validate_expected_type(folded_value, typ) - # annotate node._metadata["type"] = typ + # validate and annotate folded value + folded_value = node.get_folded_value_maybe() + if folded_value: + validate_expected_type(folded_value, typ) + folded_value._metadata["type"] = typ + def visit_Attribute(self, node: vy_ast.Attribute, typ: VyperType) -> None: _validate_msg_data_attribute(node) From 14d1e093d174c708e7fbd32906e67593e080780c Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 28 Dec 2023 16:17:45 +0800 Subject: [PATCH 115/120] read folded value from metadata --- vyper/semantics/analysis/local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 57a9e9afa9..417e9e7018 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -549,7 +549,7 @@ def visit(self, node, typ): node._metadata["type"] = typ # validate and annotate folded value - folded_value = node.get_folded_value_maybe() + folded_value = node._metadata.get("folded_value") if folded_value: validate_expected_type(folded_value, typ) folded_value._metadata["type"] = typ From 59f7d3650206d6ef9479043ff6bbef368f7511fe Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 28 Dec 2023 16:53:23 +0800 Subject: [PATCH 116/120] try reverting const amendment --- vyper/codegen/expr.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index 25927f1ab0..d42ba8babd 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -193,13 +193,6 @@ def parse_Name(self): # TODO: use self.expr._expr_info elif self.expr.id in self.context.globals: varinfo = self.context.globals[self.expr.id] - if varinfo.modifiability == Modifiability.ALWAYS_CONSTANT: - value_node = varinfo.decl_node.value - folded_value_node = value_node._metadata.get("folded_value") - if folded_value_node is not None: - value_node = folded_value_node - return Expr.parse_value_expr(value_node, self.context) - assert varinfo.modifiability == Modifiability.IMMUTABLE, "not an immutable!" ofst = varinfo.position.offset From 1f57495816a54aa14b84ae60be8791235ec661c1 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:24:12 +0800 Subject: [PATCH 117/120] revert; assert struct --- vyper/codegen/expr.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index d42ba8babd..a2c395631e 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -193,6 +193,14 @@ def parse_Name(self): # TODO: use self.expr._expr_info elif self.expr.id in self.context.globals: varinfo = self.context.globals[self.expr.id] + if varinfo.modifiability == Modifiability.ALWAYS_CONSTANT: + # non-struct constants should have been dispatched via the `Expr` ctor + # using the folded value metadata + assert isinstance(varinfo.typ, StructT) + value_node = varinfo.decl_node.value + value_node = value_node._metadata.get("folded_value", value_node) + return Expr.parse_value_expr(value_node, self.context) + assert varinfo.modifiability == Modifiability.IMMUTABLE, "not an immutable!" ofst = varinfo.position.offset From 13b341ba347958ab3fcc92e9c70eb916206cb0e1 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:31:49 +0800 Subject: [PATCH 118/120] clean up expr codegen ctor --- vyper/codegen/expr.py | 9 +--- .../function_definitions/external_function.py | 51 ++++++++----------- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index a2c395631e..27266577a0 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -70,13 +70,8 @@ class Expr: # TODO: Once other refactors are made reevaluate all inline imports def __init__(self, node, context): - # use original node for better diagnostics - orig_node = node if isinstance(node, vy_ast.VyperNode): - folded_node = node._metadata.get("folded_value") - if folded_node: - folded_node._metadata["type"] = node._metadata["type"] - node = folded_node + node = node._metadata.get("folded_value", node) self.expr = node self.context = context @@ -89,7 +84,7 @@ def __init__(self, node, context): return fn_name = f"parse_{type(node).__name__}" - with tag_exceptions(orig_node, fallback_exception_type=CodegenPanic, note=fn_name): + with tag_exceptions(node, fallback_exception_type=CodegenPanic, note=fn_name): fn = getattr(self, fn_name) self.ir_node = fn() assert isinstance(self.ir_node, IRnode), self.ir_node diff --git a/vyper/codegen/function_definitions/external_function.py b/vyper/codegen/function_definitions/external_function.py index f1a6a1ee1d..9a66708872 100644 --- a/vyper/codegen/function_definitions/external_function.py +++ b/vyper/codegen/function_definitions/external_function.py @@ -1,4 +1,3 @@ -from vyper import ast as vy_ast from vyper.codegen.abi_encoder import abi_encoding_matches_vyper from vyper.codegen.context import Context, VariableRecord from vyper.codegen.core import get_element_ptr, getpos, make_setter, needs_clamp @@ -8,7 +7,7 @@ from vyper.codegen.stmt import parse_body from vyper.evm.address_space import CALLDATA, DATA, MEMORY from vyper.semantics.types import TupleT -from vyper.semantics.types.function import ContractFunctionT, KeywordArg, PositionalArg +from vyper.semantics.types.function import ContractFunctionT # register function args with the local calling context. @@ -51,7 +50,7 @@ def _register_function_args(func_t: ContractFunctionT, context: Context) -> list def _generate_kwarg_handlers( - func_t: ContractFunctionT, context: Context, code: vy_ast.FunctionDef + func_t: ContractFunctionT, context: Context ) -> dict[str, tuple[int, IRnode]]: # generate kwarg handlers. # since they might come in thru calldata or be default, @@ -63,14 +62,12 @@ def _generate_kwarg_handlers( # write default args to memory # goto external_function_common_ir - def handler_for(calldata_kwargs_info, default_kwargs_info, default_kwargs): - calldata_args_info: list[Union[PositionalArg, KeywordArg]] = ( - func_t.positional_args + calldata_kwargs_info - ) + def handler_for(calldata_kwargs, default_kwargs): + calldata_args = func_t.positional_args + calldata_kwargs # create a fake type so that get_element_ptr works - calldata_args_t = TupleT(list(arg.typ for arg in calldata_args_info)) + calldata_args_t = TupleT(list(arg.typ for arg in calldata_args)) - abi_sig = func_t.abi_signature_for_kwargs(calldata_kwargs_info) + abi_sig = func_t.abi_signature_for_kwargs(calldata_kwargs) calldata_kwargs_ofst = IRnode( 4, location=CALLDATA, typ=calldata_args_t, encoding=Encoding.ABI @@ -84,10 +81,10 @@ def handler_for(calldata_kwargs_info, default_kwargs_info, default_kwargs): calldata_min_size = args_abi_t.min_size() + 4 # TODO optimize make_setter by using - # TupleT(list(arg.typ for arg in calldata_kwargs_info + default_kwargs_info)) + # TupleT(list(arg.typ for arg in calldata_kwargs + default_kwargs)) # (must ensure memory area is contiguous) - for i, arg_meta in enumerate(calldata_kwargs_info): + for i, arg_meta in enumerate(calldata_kwargs): k = func_t.n_positional_args + i dst = context.lookup_var(arg_meta.name).pos @@ -100,15 +97,15 @@ def handler_for(calldata_kwargs_info, default_kwargs_info, default_kwargs): copy_arg.source_pos = getpos(arg_meta.ast_source) ret.append(copy_arg) - for x, y in zip(default_kwargs_info, default_kwargs): + for x in default_kwargs: dst = context.lookup_var(x.name).pos lhs = IRnode(dst, location=MEMORY, typ=x.typ) - lhs.source_pos = getpos(y) - kw_ast_val = y + lhs.source_pos = getpos(x.ast_source) + kw_ast_val = func_t.default_values[x.name] # e.g. `3` in x: int = 3 rhs = Expr(kw_ast_val, context).ir_node copy_arg = make_setter(lhs, rhs) - copy_arg.source_pos = getpos(y) + copy_arg.source_pos = getpos(x.ast_source) ret.append(copy_arg) ret.append(["goto", func_t._ir_info.external_function_base_entry_label]) @@ -118,24 +115,20 @@ def handler_for(calldata_kwargs_info, default_kwargs_info, default_kwargs): ret = {} - keyword_args_info = func_t.keyword_args - keyword_args = code.args.defaults + keyword_args = func_t.keyword_args - # allocate keyword_args_info slots in memory - for arg in keyword_args_info: + # allocate variable slots in memory + for arg in keyword_args: context.new_variable(arg.name, arg.typ, is_mutable=False) - for i, _ in enumerate(keyword_args_info): - calldata_kwargs_info = keyword_args_info[:i] - default_kwargs_info: list[KeywordArg] = keyword_args_info[i:] - default_kwargs: list[vy_ast.VyperNode] = keyword_args[i:] + for i, _ in enumerate(keyword_args): + calldata_kwargs = keyword_args[:i] + default_kwargs = keyword_args[i:] - sig, calldata_min_size, ir_node = handler_for( - calldata_kwargs_info, default_kwargs_info, default_kwargs - ) + sig, calldata_min_size, ir_node = handler_for(calldata_kwargs, default_kwargs) ret[sig] = calldata_min_size, ir_node - sig, calldata_min_size, ir_node = handler_for(keyword_args_info, [], []) + sig, calldata_min_size, ir_node = handler_for(keyword_args, []) ret[sig] = calldata_min_size, ir_node @@ -160,7 +153,7 @@ def generate_ir_for_external_function(code, func_t, context): handle_base_args = _register_function_args(func_t, context) # generate handlers for kwargs and register the variable records - kwarg_handlers = _generate_kwarg_handlers(func_t, context, code) + kwarg_handlers = _generate_kwarg_handlers(func_t, context) body = ["seq"] # once optional args have been handled, @@ -192,4 +185,4 @@ def generate_ir_for_external_function(code, func_t, context): # besides any kwarg handling func_common_ir = IRnode.from_list(["seq", body, exit_], source_pos=getpos(code)) - return kwarg_handlers, func_common_ir + return kwarg_handlers, func_common_ir \ No newline at end of file From 1709d3a02abfe5279e9510b635d74fe963a852d6 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:32:16 +0800 Subject: [PATCH 119/120] relax module node for exception str --- vyper/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/exceptions.py b/vyper/exceptions.py index f216069eab..f625a7d3fb 100644 --- a/vyper/exceptions.py +++ b/vyper/exceptions.py @@ -108,7 +108,7 @@ def __str__(self): if isinstance(node, vy_ast.VyperNode): module_node = node.get_ancestor(vy_ast.Module) - if module_node.get("path") not in (None, ""): + if module_node and module_node.get("path") not in (None, ""): node_msg = f'{node_msg}contract "{module_node.path}:{node.lineno}", ' fn_node = node.get_ancestor(vy_ast.FunctionDef) From fe925d69127da3bb9074ca63694545ef55a6d189 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:32:32 +0800 Subject: [PATCH 120/120] fix lint --- vyper/codegen/function_definitions/external_function.py | 2 +- vyper/compiler/phases.py | 2 +- vyper/semantics/analysis/module.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vyper/codegen/function_definitions/external_function.py b/vyper/codegen/function_definitions/external_function.py index 9a66708872..65276469e7 100644 --- a/vyper/codegen/function_definitions/external_function.py +++ b/vyper/codegen/function_definitions/external_function.py @@ -185,4 +185,4 @@ def generate_ir_for_external_function(code, func_t, context): # besides any kwarg handling func_common_ir = IRnode.from_list(["seq", body, exit_], source_pos=getpos(code)) - return kwarg_handlers, func_common_ir \ No newline at end of file + return kwarg_handlers, func_common_ir diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 184c60c113..7407c4f281 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -280,7 +280,7 @@ def generate_folded_ast( symbol_tables = set_data_positions(vyper_module, storage_layout_overrides) vyper_module_folded = copy.deepcopy(vyper_module) - #vy_ast.folding.fold(vyper_module_folded) + # vy_ast.folding.fold(vyper_module_folded) return vyper_module_folded, symbol_tables diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index fa2ac73724..9d828eaa2d 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -495,7 +495,7 @@ def _parse_and_fold_ast(file: FileInput) -> vy_ast.VyperNode: resolved_path=str(file.resolved_path), ) vy_ast.validation.validate_literal_nodes(ret) - #vy_ast.folding.fold(ret) + # vy_ast.folding.fold(ret) return ret