finder.py 5.2 KB
Newer Older
Stelios Karozis's avatar
Stelios Karozis committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
"""
Searching for names with given scope and name. This is very central in Jedi and
Python. The name resolution is quite complicated with descripter,
``__getattribute__``, ``__getattr__``, ``global``, etc.

If you want to understand name resolution, please read the first few chapters
in http://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/.

Flow checks
+++++++++++

Flow checks are not really mature. There's only a check for ``isinstance``.  It
would check whether a flow has the form of ``if isinstance(a, type_or_tuple)``.
Unfortunately every other thing is being ignored (e.g. a == '' would be easy to
check for -> a is a string). There's big potential in these checks.
"""

from parso.tree import search_ancestor
from parso.python.tree import Name

from jedi import settings
from jedi.inference.arguments import TreeArguments
from jedi.inference.value import iterable
from jedi.inference.base_value import NO_VALUES
from jedi.parser_utils import is_scope


def filter_name(filters, name_or_str):
    """
    Searches names that are defined in a scope (the different
    ``filters``), until a name fits.
    """
    string_name = name_or_str.value if isinstance(name_or_str, Name) else name_or_str
    names = []
    for filter in filters:
        names = filter.get(string_name)
        if names:
            break

    return list(_remove_del_stmt(names))


def _remove_del_stmt(names):
    # Catch del statements and remove them from results.
    for name in names:
        if name.tree_name is not None:
            definition = name.tree_name.get_definition()
            if definition is not None and definition.type == 'del_stmt':
                continue
        yield name


def check_flow_information(value, flow, search_name, pos):
    """ Try to find out the type of a variable just with the information that
    is given by the flows: e.g. It is also responsible for assert checks.::

        if isinstance(k, str):
            k.  # <- completion here

    ensures that `k` is a string.
    """
    if not settings.dynamic_flow_information:
        return None

    result = None
    if is_scope(flow):
        # Check for asserts.
        module_node = flow.get_root_node()
        try:
            names = module_node.get_used_names()[search_name.value]
        except KeyError:
            return None
        names = reversed([
            n for n in names
            if flow.start_pos <= n.start_pos < (pos or flow.end_pos)
        ])

        for name in names:
            ass = search_ancestor(name, 'assert_stmt')
            if ass is not None:
                result = _check_isinstance_type(value, ass.assertion, search_name)
                if result is not None:
                    return result

    if flow.type in ('if_stmt', 'while_stmt'):
        potential_ifs = [c for c in flow.children[1::4] if c != ':']
        for if_test in reversed(potential_ifs):
            if search_name.start_pos > if_test.end_pos:
                return _check_isinstance_type(value, if_test, search_name)
    return result


def _get_isinstance_trailer_arglist(node):
    if node.type in ('power', 'atom_expr') and len(node.children) == 2:
        # This might be removed if we analyze and, etc
        first, trailer = node.children
        if first.type == 'name' and first.value == 'isinstance' \
                and trailer.type == 'trailer' and trailer.children[0] == '(':
            return trailer
    return None


def _check_isinstance_type(value, node, search_name):
    lazy_cls = None
    trailer = _get_isinstance_trailer_arglist(node)
    if trailer is not None and len(trailer.children) == 3:
        arglist = trailer.children[1]
        args = TreeArguments(value.inference_state, value, arglist, trailer)
        param_list = list(args.unpack())
        # Disallow keyword arguments
        if len(param_list) == 2 and len(arglist.children) == 3:
            (key1, _), (key2, lazy_value_cls) = param_list
            if key1 is None and key2 is None:
                call = _get_call_string(search_name)
                is_instance_call = _get_call_string(arglist.children[0])
                # Do a simple get_code comparison of the strings . They should
                # just have the same code, and everything will be all right.
                # There are ways that this is not correct, if some stuff is
                # redefined in between. However here we don't care, because
                # it's a heuristic that works pretty well.
                if call == is_instance_call:
                    lazy_cls = lazy_value_cls
    if lazy_cls is None:
        return None

    value_set = NO_VALUES
    for cls_or_tup in lazy_cls.infer():
        if isinstance(cls_or_tup, iterable.Sequence) and cls_or_tup.array_type == 'tuple':
            for lazy_value in cls_or_tup.py__iter__():
                value_set |= lazy_value.infer().execute_with_values()
        else:
            value_set |= cls_or_tup.execute_with_values()
    return value_set


def _get_call_string(node):
    if node.parent.type == 'atom_expr':
        return _get_call_string(node.parent)

    code = ''
    leaf = node.get_first_leaf()
    end = node.get_last_leaf().end_pos
    while leaf.start_pos < end:
        code += leaf.value
        leaf = leaf.get_next_leaf()
    return code