]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | from typing import Dict, Optional |
2 | ||
3 | from jedi.parser_utils import get_flow_branch_keyword, is_scope, get_parent_scope | |
4 | from jedi.inference.recursion import execution_allowed | |
5 | from jedi.inference.helpers import is_big_annoying_library | |
6 | ||
7 | ||
8 | class Status: | |
9 | lookup_table: Dict[Optional[bool], 'Status'] = {} | |
10 | ||
11 | def __init__(self, value: Optional[bool], name: str) -> None: | |
12 | self._value = value | |
13 | self._name = name | |
14 | Status.lookup_table[value] = self | |
15 | ||
16 | def invert(self): | |
17 | if self is REACHABLE: | |
18 | return UNREACHABLE | |
19 | elif self is UNREACHABLE: | |
20 | return REACHABLE | |
21 | else: | |
22 | return UNSURE | |
23 | ||
24 | def __and__(self, other): | |
25 | if UNSURE in (self, other): | |
26 | return UNSURE | |
27 | else: | |
28 | return REACHABLE if self._value and other._value else UNREACHABLE | |
29 | ||
30 | def __repr__(self): | |
31 | return '<%s: %s>' % (type(self).__name__, self._name) | |
32 | ||
33 | ||
34 | REACHABLE = Status(True, 'reachable') | |
35 | UNREACHABLE = Status(False, 'unreachable') | |
36 | UNSURE = Status(None, 'unsure') | |
37 | ||
38 | ||
39 | def _get_flow_scopes(node): | |
40 | while True: | |
41 | node = get_parent_scope(node, include_flows=True) | |
42 | if node is None or is_scope(node): | |
43 | return | |
44 | yield node | |
45 | ||
46 | ||
47 | def reachability_check(context, value_scope, node, origin_scope=None): | |
48 | if is_big_annoying_library(context) \ | |
49 | or not context.inference_state.flow_analysis_enabled: | |
50 | return UNSURE | |
51 | ||
52 | first_flow_scope = get_parent_scope(node, include_flows=True) | |
53 | if origin_scope is not None: | |
54 | origin_flow_scopes = list(_get_flow_scopes(origin_scope)) | |
55 | node_flow_scopes = list(_get_flow_scopes(node)) | |
56 | ||
57 | branch_matches = True | |
58 | for flow_scope in origin_flow_scopes: | |
59 | if flow_scope in node_flow_scopes: | |
60 | node_keyword = get_flow_branch_keyword(flow_scope, node) | |
61 | origin_keyword = get_flow_branch_keyword(flow_scope, origin_scope) | |
62 | branch_matches = node_keyword == origin_keyword | |
63 | if flow_scope.type == 'if_stmt': | |
64 | if not branch_matches: | |
65 | return UNREACHABLE | |
66 | elif flow_scope.type == 'try_stmt': | |
67 | if not branch_matches and origin_keyword == 'else' \ | |
68 | and node_keyword == 'except': | |
69 | return UNREACHABLE | |
70 | if branch_matches: | |
71 | break | |
72 | ||
73 | # Direct parents get resolved, we filter scopes that are separate | |
74 | # branches. This makes sense for autocompletion and static analysis. | |
75 | # For actual Python it doesn't matter, because we're talking about | |
76 | # potentially unreachable code. | |
77 | # e.g. `if 0:` would cause all name lookup within the flow make | |
78 | # unaccessible. This is not a "problem" in Python, because the code is | |
79 | # never called. In Jedi though, we still want to infer types. | |
80 | while origin_scope is not None: | |
81 | if first_flow_scope == origin_scope and branch_matches: | |
82 | return REACHABLE | |
83 | origin_scope = origin_scope.parent | |
84 | ||
85 | return _break_check(context, value_scope, first_flow_scope, node) | |
86 | ||
87 | ||
88 | def _break_check(context, value_scope, flow_scope, node): | |
89 | reachable = REACHABLE | |
90 | if flow_scope.type == 'if_stmt': | |
91 | if flow_scope.is_node_after_else(node): | |
92 | for check_node in flow_scope.get_test_nodes(): | |
93 | reachable = _check_if(context, check_node) | |
94 | if reachable in (REACHABLE, UNSURE): | |
95 | break | |
96 | reachable = reachable.invert() | |
97 | else: | |
98 | flow_node = flow_scope.get_corresponding_test_node(node) | |
99 | if flow_node is not None: | |
100 | reachable = _check_if(context, flow_node) | |
101 | elif flow_scope.type in ('try_stmt', 'while_stmt'): | |
102 | return UNSURE | |
103 | ||
104 | # Only reachable branches need to be examined further. | |
105 | if reachable in (UNREACHABLE, UNSURE): | |
106 | return reachable | |
107 | ||
108 | if value_scope != flow_scope and value_scope != flow_scope.parent: | |
109 | flow_scope = get_parent_scope(flow_scope, include_flows=True) | |
110 | return reachable & _break_check(context, value_scope, flow_scope, node) | |
111 | else: | |
112 | return reachable | |
113 | ||
114 | ||
115 | def _check_if(context, node): | |
116 | with execution_allowed(context.inference_state, node) as allowed: | |
117 | if not allowed: | |
118 | return UNSURE | |
119 | ||
120 | types = context.infer_node(node) | |
121 | values = set(x.py__bool__() for x in types) | |
122 | if len(values) == 1: | |
123 | return Status.lookup_table[values.pop()] | |
124 | else: | |
125 | return UNSURE |