File: flow_analysis.py

package info (click to toggle)
python-jedi 0.10.0~git1%2Bf05c071-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 2,064 kB
  • ctags: 3,014
  • sloc: python: 16,997; makefile: 149; ansic: 13
file content (91 lines) | stat: -rw-r--r-- 3,126 bytes parent folder | download
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
from jedi.parser import tree


class Status(object):
    lookup_table = {}

    def __init__(self, value, name):
        self._value = value
        self._name = name
        Status.lookup_table[value] = self

    def invert(self):
        if self is REACHABLE:
            return UNREACHABLE
        elif self is UNREACHABLE:
            return REACHABLE
        else:
            return UNSURE

    def __and__(self, other):
        if UNSURE in (self, other):
            return UNSURE
        else:
            return REACHABLE if self._value and other._value else UNREACHABLE

    def __repr__(self):
        return '<%s: %s>' % (type(self).__name__, self._name)


REACHABLE = Status(True, 'reachable')
UNREACHABLE = Status(False, 'unreachable')
UNSURE = Status(None, 'unsure')


def break_check(evaluator, base_scope, stmt, origin_scope=None):
    element_scope = evaluator.wrap(stmt.get_parent_scope(include_flows=True))
    # Direct parents get resolved, we filter scopes that are separate branches.
    # This makes sense for autocompletion and static analysis. For actual
    # Python it doesn't matter, because we're talking about potentially
    # unreachable code.
    # e.g. `if 0:` would cause all name lookup within the flow make
    # unaccessible. This is not a "problem" in Python, because the code is
    # never called. In Jedi though, we still want to infer types.
    while origin_scope is not None:
        if element_scope == origin_scope:
            return REACHABLE
        origin_scope = origin_scope.parent
    x = _break_check(evaluator, stmt, base_scope, element_scope)
    return x


def _break_check(evaluator, stmt, base_scope, element_scope):
    element_scope = evaluator.wrap(element_scope)
    base_scope = evaluator.wrap(base_scope)

    reachable = REACHABLE
    if isinstance(element_scope, tree.IfStmt):
        if element_scope.node_after_else(stmt):
            for check_node in element_scope.check_nodes():
                reachable = _check_if(evaluator, check_node)
                if reachable in (REACHABLE, UNSURE):
                    break
            reachable = reachable.invert()
        else:
            node = element_scope.node_in_which_check_node(stmt)
            if node is not None:
                reachable = _check_if(evaluator, node)
    elif isinstance(element_scope, (tree.TryStmt, tree.WhileStmt)):
        return UNSURE

    # Only reachable branches need to be examined further.
    if reachable in (UNREACHABLE, UNSURE):
        return reachable

    if element_scope.type == 'file_input':
        # The definition is in another module and therefore just return what we
        # have generated.
        return reachable
    if base_scope != element_scope and base_scope != element_scope.parent:
        return reachable & _break_check(evaluator, stmt, base_scope, element_scope.parent)
    else:
        return reachable


def _check_if(evaluator, node):
    types = evaluator.eval_element(node)
    values = set(x.py__bool__() for x in types)
    if len(values) == 1:
        return Status.lookup_table[values.pop()]
    else:
        return UNSURE