diff --git a/tests/parser/syntax/test_constants.py b/tests/parser/syntax/test_constants.py index ffd2f1faa0..df3f06901f 100644 --- a/tests/parser/syntax/test_constants.py +++ b/tests/parser/syntax/test_constants.py @@ -171,6 +171,26 @@ def hello() : """, ImmutableViolation, ), + ( + """ +a: constant(DynArray[uint128, 2]) = [0] + +@external +def foo(): + a.pop() + """, + ImmutableViolation, + ), + ( + """ +a: constant(DynArray[uint128, 2]) = [0] + +@external +def foo(): + a.append(1) + """, + ImmutableViolation, + ), ] diff --git a/vyper/ast/folding.py b/vyper/ast/folding.py index fbd1dfc2f4..e7daf5839d 100644 --- a/vyper/ast/folding.py +++ b/vyper/ast/folding.py @@ -4,7 +4,7 @@ from vyper.ast import nodes as vy_ast from vyper.builtins.functions import DISPATCH_TABLE from vyper.exceptions import UnfoldableNode, UnknownType -from vyper.semantics.types.base import VyperType +from vyper.semantics.analysis.base import DataLocation, ExprInfo from vyper.semantics.types.utils import type_from_annotation from vyper.utils import SizeLimits @@ -180,8 +180,16 @@ def replace_user_defined_constants(vyper_module: vy_ast.Module) -> int: # Extract type definition from propagated annotation type_ = None + expr_info = None try: type_ = type_from_annotation(node.annotation) + expr_info = ExprInfo( + type_, + location=DataLocation.CODE, + is_constant=node.is_constant, + is_immutable=node.is_immutable, + ) + except UnknownType: # handle user-defined types e.g. structs - it's OK to not # propagate the type annotation here because user-defined @@ -189,7 +197,7 @@ def replace_user_defined_constants(vyper_module: vy_ast.Module) -> int: pass changed_nodes += replace_constant( - vyper_module, node.target.id, node.value, False, type_=type_ + vyper_module, node.target.id, node.value, False, expr_info=expr_info ) return changed_nodes @@ -198,18 +206,27 @@ 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, expr_info=None): 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_ + if expr_info is not None: + new_node._metadata["exprinfo"] = expr_info 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] + base_type_exprinfo = None + if expr_info is not None: + base_type_exprinfo = ExprInfo( + typ=expr_info.typ.value_type, + location=DataLocation.CODE, + is_constant=expr_info.is_constant, + is_immutable=expr_info.is_immutable, + ) + list_values = [ + _replace(old_node, i, expr_info=base_type_exprinfo) for i in new_node.elements + ] new_node = new_node.from_node(old_node, elements=list_values) - if type_: - new_node._metadata["type"] = type_ + if expr_info is not None: + new_node._metadata["exprinfo"] = expr_info return new_node elif isinstance(new_node, vy_ast.Call): # Replace `Name` node with `Call` node @@ -231,7 +248,7 @@ def replace_constant( id_: str, replacement_node: Union[vy_ast.Constant, vy_ast.List, vy_ast.Call], raise_on_error: bool, - type_: Optional[VyperType] = None, + expr_info: Optional[ExprInfo] = None, ) -> int: """ Replace references to a variable name with a literal value. @@ -247,8 +264,9 @@ def replace_constant( `Call` nodes are for struct constants. raise_on_error: bool Boolean indicating if `UnfoldableNode` exception should be raised or ignored. - type_ : VyperType, optional - Type definition to be propagated to type checker. + expr_info : ExprInfo, optional + Type definition plus associated metadata like constancy attributes to be + propagated to type checker. Returns ------- @@ -284,7 +302,7 @@ def replace_constant( try: # note: _replace creates a copy of the replacement_node - new_node = _replace(node, replacement_node, type_=type_) + new_node = _replace(node, replacement_node, expr_info=expr_info) except UnfoldableNode: if raise_on_error: raise diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 26f3fd1827..62b8749ed3 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -62,6 +62,11 @@ def __init__(self): self.namespace = get_namespace() def get_expr_info(self, node: vy_ast.VyperNode) -> ExprInfo: + # Early termination if variable info is propagated in metadata + exprinfo = node._metadata.get("exprinfo") + if exprinfo is not None: + return exprinfo + t = self.get_exact_type_from_node(node) # if it's a Name, we have varinfo for it @@ -144,6 +149,8 @@ def get_possible_types_from_node(self, node, include_type_exprs=False): A list of type objects """ # Early termination if typedef is propagated in metadata + if "exprinfo" in node._metadata: + return [node._metadata["exprinfo"].typ] if "type" in node._metadata: return [node._metadata["type"]]