Esempio n. 1
0
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
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 4
0
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
Esempio n. 5
0
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
Esempio n. 6
0
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
Esempio n. 7
0
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
Esempio n. 8
0
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)
Esempio n. 9
0
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()
Esempio n. 10
0
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
Esempio n. 11
0
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)
Esempio n. 12
0
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
Esempio n. 13
0
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