def replace_literal_ops(vyper_module: vy_ast.Module) -> int: """ Find and evaluate operation and comparison nodes within the Vyper AST, replacing them with Constant nodes where possible. Arguments --------- vyper_module : Module Top-level Vyper AST node. Returns ------- int Number of nodes that were replaced. """ changed_nodes = 0 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() except UnfoldableNode: continue changed_nodes += 1 vyper_module.replace_in_tree(node, new_node) return changed_nodes
def replace_subscripts(vyper_module: vy_ast.Module) -> int: """ Find and evaluate Subscript nodes within the Vyper AST, replacing them with Constant nodes where possible. Arguments --------- vyper_module : Module Top-level Vyper AST node. Returns ------- int Number of nodes that were replaced. """ changed_nodes = 0 for node in vyper_module.get_descendants(vy_ast.Subscript, reverse=True): try: new_node = node.evaluate() except UnfoldableNode: continue changed_nodes += 1 vyper_module.replace_in_tree(node, new_node) return changed_nodes
def replace_builtin_functions(vyper_module: vy_ast.Module) -> int: """ Find and evaluate builtin function calls within the Vyper AST, replacing them with Constant nodes where possible. Arguments --------- vyper_module : Module Top-level Vyper AST node. Returns ------- int Number of nodes that were replaced. """ changed_nodes = 0 for node in vyper_module.get_descendants(vy_ast.Call, reverse=True): if not isinstance(node.func, vy_ast.Name): continue name = node.func.id func = DISPATCH_TABLE.get(name) if func is None or not hasattr(func, "evaluate"): continue try: new_node = func.evaluate(node) # type: ignore except UnfoldableNode: continue changed_nodes += 1 vyper_module.replace_in_tree(node, new_node) return changed_nodes
def replace_constant( vyper_module: vy_ast.Module, id_: str, replacement_node: Union[vy_ast.Constant, vy_ast.List], raise_on_error: bool, ) -> int: """ Replace references to a variable name with a literal value. Arguments --------- vyper_module : Module Module-level ast node to perform replacement in. id_ : str String representing the `.id` attribute of the node(s) to be replaced. replacement_node : Constant | List Vyper ast node representing the literal value to be substituted in. raise_on_error: bool Boolean indicating if `UnfoldableNode` exception should be raised or ignored. Returns ------- int Number of nodes that were replaced. """ changed_nodes = 0 for node in vyper_module.get_descendants(vy_ast.Name, {"id": id_}, reverse=True): parent = node.get_ancestor() if isinstance(parent, vy_ast.Attribute): # do not replace attributes continue if isinstance(parent, vy_ast.Call) and node == parent.func: # do not replace calls continue # do not replace dictionary keys if isinstance(parent, vy_ast.Dict) and node in parent.keys: continue if not isinstance(parent, vy_ast.Index): # do not replace left-hand side of assignments assign = node.get_ancestor( (vy_ast.Assign, vy_ast.AnnAssign, vy_ast.AugAssign) ) if assign and node in assign.target.get_descendants(include_self=True): continue try: new_node = _replace(node, replacement_node) except UnfoldableNode: if raise_on_error: raise continue changed_nodes += 1 vyper_module.replace_in_tree(node, new_node) return changed_nodes
def replace_user_defined_constants(vyper_module: vy_ast.Module) -> int: """ Find user-defined constant assignments, and replace references to the constants with their literal values. Arguments --------- vyper_module : Module Top-level Vyper AST node. Returns ------- int Number of nodes that were replaced. """ changed_nodes = 0 for node in vyper_module.get_children(vy_ast.AnnAssign): if not isinstance(node.target, vy_ast.Name): # left-hand-side of assignment is not a variable continue if node.get("annotation.func.id") != "constant": # annotation is not wrapped in `constant(...)` continue changed_nodes += replace_constant(vyper_module, node.target.id, node.value, False) return changed_nodes
def parse_natspec( vyper_module: vy_ast.Module, global_ctx: GlobalContext, ) -> Tuple[dict, dict]: """ Parses NatSpec documentation from a contract. Arguments --------- vyper_module : Module Module-level vyper ast node. interface_codes: Dict, optional Dict containing relevant data for any import statements related to this contract. Returns ------- dict NatSpec user documentation dict NatSpec developer documentation """ userdoc, devdoc = {}, {} source: str = vyper_module.full_source_code docstring = vyper_module.get("doc_string.value") if docstring: devdoc.update(_parse_docstring(source, docstring, ("param", "return"))) if "notice" in devdoc: userdoc["notice"] = devdoc.pop("notice") for node in [i for i in vyper_module.body if i.get("doc_string.value")]: docstring = node.doc_string.value sigs = sig_utils.mk_single_method_identifier(node, global_ctx) if isinstance(node.returns, vy_ast.Tuple): ret_len = len(node.returns.elements) elif node.returns: ret_len = 1 else: ret_len = 0 if sigs: args = tuple(i.arg for i in node.args.args) invalid_fields = ( "title", "license", ) fn_natspec = _parse_docstring(source, docstring, invalid_fields, args, ret_len) for s in sigs: if "notice" in fn_natspec: userdoc.setdefault("methods", {})[s] = { "notice": fn_natspec.pop("notice") } if fn_natspec: devdoc.setdefault("methods", {})[s] = fn_natspec return userdoc, devdoc
def parse_natspec(vyper_module_folded: vy_ast.Module) -> Tuple[dict, dict]: """ Parses NatSpec documentation from a contract. Arguments --------- vyper_module_folded : Module Module-level vyper ast node. interface_codes: Dict, optional Dict containing relevant data for any import statements related to this contract. Returns ------- dict NatSpec user documentation dict NatSpec developer documentation """ from vyper.semantics.types.function import FunctionVisibility userdoc, devdoc = {}, {} source: str = vyper_module_folded.full_source_code docstring = vyper_module_folded.get("doc_string.value") if docstring: devdoc.update(_parse_docstring(source, docstring, ("param", "return"))) if "notice" in devdoc: userdoc["notice"] = devdoc.pop("notice") for node in [ i for i in vyper_module_folded.body if i.get("doc_string.value") ]: docstring = node.doc_string.value func_type = node._metadata["type"] if func_type.visibility != FunctionVisibility.EXTERNAL: continue if isinstance(node.returns, vy_ast.Tuple): ret_len = len(node.returns.elements) elif node.returns: ret_len = 1 else: ret_len = 0 args = tuple(i.arg for i in node.args.args) invalid_fields = ("title", "license") fn_natspec = _parse_docstring(source, docstring, invalid_fields, args, ret_len) for method_id in func_type.method_ids: if "notice" in fn_natspec: userdoc.setdefault("methods", {})[method_id] = { "notice": fn_natspec.pop("notice") } if fn_natspec: devdoc.setdefault("methods", {})[method_id] = fn_natspec return userdoc, devdoc
def replace_constant( vyper_ast_node: vy_ast.Module, id_: str, replacement_node: Union[vy_ast.Constant, vy_ast.List], ) -> None: """ Replace references to a variable name with a literal value. Arguments --------- vyper_ast_node : Module Module-level ast node to perform replacement in. id_ : str String representing the `.id` attribute of the node(s) to be replaced. replacement_node : Constant | List Vyper ast node representing the literal value to be substituted in. """ for node in vyper_ast_node.get_descendants(vy_ast.Name, {'id': id_}, reverse=True): # do not replace attributes or calls if isinstance(node.get_ancestor(), (vy_ast.Attribute, vy_ast.Call)): continue # do not replace dictionary keys if isinstance(node.get_ancestor(), vy_ast.Dict) and node in node.get_ancestor().keys: continue if not isinstance(node.get_ancestor(), vy_ast.Index): # do not replace left-hand side of assignments parent = node.get_ancestor( (vy_ast.Assign, vy_ast.AnnAssign, vy_ast.AugAssign)) if parent and node in parent.target.get_descendants( include_self=True): continue new_node = _replace(node, replacement_node) vyper_ast_node.replace_in_tree(node, new_node)
def validate_literal_nodes(vyper_module: vy_ast.Module) -> None: """ Individually validate Vyper AST nodes. Calls the `validate` method of each node to verify that literal nodes do not contain invalid values. Arguments --------- vyper_module : vy_ast.Module Top level Vyper AST node. """ for node in vyper_module.get_descendants(): if hasattr(node, "validate"): node.validate()
def replace_user_defined_constants(vyper_module: vy_ast.Module) -> int: """ Find user-defined constant assignments, and replace references to the constants with their literal values. Arguments --------- vyper_module : Module Top-level Vyper AST node. Returns ------- int Number of nodes that were replaced. """ changed_nodes = 0 for node in vyper_module.get_children(vy_ast.AnnAssign): if not isinstance(node.target, vy_ast.Name): # left-hand-side of assignment is not a variable continue if node.get("annotation.func.id") != "constant": # annotation is not wrapped in `constant(...)` continue # Extract type definition from propagated annotation constant_annotation = node.get("annotation.args")[0] try: type_ = ( get_type_from_annotation(constant_annotation, DataLocation.UNSET) if constant_annotation else None ) 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 type_ = None changed_nodes += replace_constant( vyper_module, node.target.id, node.value, False, type_=type_ ) return changed_nodes
def replace_user_defined_constants(vyper_ast_node: vy_ast.Module) -> None: """ Find user-defined constant assignments, and replace references to the constants with their literal values. Arguments --------- vyper_ast_node : Module Top-level Vyper AST node. """ for node in vyper_ast_node.get_children(vy_ast.AnnAssign): if not isinstance(node.target, vy_ast.Name): # left-hand-side of assignment is not a variable continue if node.get('annotation.func.id') != "constant": # annotation is not wrapped in `constant(...)` continue replace_constant(vyper_ast_node, node.target.id, node.value)
def replace_user_defined_constants(vyper_module: vy_ast.Module) -> int: """ Find user-defined constant assignments, and replace references to the constants with their literal values. Arguments --------- vyper_module : Module Top-level Vyper AST node. Returns ------- int Number of nodes that were replaced. """ changed_nodes = 0 for node in vyper_module.get_children(vy_ast.AnnAssign): if not isinstance(node.target, vy_ast.Name): # left-hand-side of assignment is not a variable continue if node.get("annotation.func.id") != "constant": # annotation is not wrapped in `constant(...)` continue # Extract type definition from propagated annotation constant_annotation = node.get("annotation.args")[0] type_ = (get_type_from_annotation(constant_annotation, DataLocation.UNSET) if constant_annotation else None) changed_nodes += replace_constant(vyper_module, node.target.id, node.value, False, type_=type_) return changed_nodes
def replace_constant( vyper_module: vy_ast.Module, id_: str, replacement_node: Union[vy_ast.Constant, vy_ast.List, vy_ast.Call], raise_on_error: bool, type_: BaseTypeDefinition = None, ) -> int: """ Replace references to a variable name with a literal value. Arguments --------- vyper_module : Module Module-level ast node to perform replacement in. id_ : str String representing the `.id` attribute of the node(s) to be replaced. replacement_node : Constant | List | Call Vyper ast node representing the literal value to be substituted in. `Call` nodes are for struct constants. raise_on_error: bool Boolean indicating if `UnfoldableNode` exception should be raised or ignored. type_ : BaseTypeDefinition, optional Type definition to be propagated to type checker. Returns ------- int Number of nodes that were replaced. """ is_struct = False if isinstance(replacement_node, vy_ast.Call) and len(replacement_node.args) == 1: if isinstance(replacement_node.args[0], vy_ast.Dict): is_struct = True changed_nodes = 0 for node in vyper_module.get_descendants(vy_ast.Name, {"id": id_}, reverse=True): parent = node.get_ancestor() if isinstance(parent, vy_ast.Call) and node == parent.func: # do not replace calls that are not structs if not is_struct: continue # do not replace dictionary keys if isinstance(parent, vy_ast.Dict) and node in parent.keys: continue if not node.get_ancestor(vy_ast.Index): # do not replace left-hand side of assignments assign = node.get_ancestor((vy_ast.Assign, vy_ast.AnnAssign, vy_ast.AugAssign)) if assign and node in assign.target.get_descendants(include_self=True): continue try: # Codegen may mutate AST (particularly structs). replacement_node = copy.deepcopy(replacement_node) new_node = _replace(node, replacement_node, type_=type_) except UnfoldableNode: if raise_on_error: raise continue changed_nodes += 1 vyper_module.replace_in_tree(node, new_node) return changed_nodes