85 lines
2.8 KiB
Python
85 lines
2.8 KiB
Python
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
|
|
return _break_check(evaluator, stmt, base_scope, element_scope)
|
|
|
|
|
|
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)
|
|
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 base_scope != element_scope and base_scope != element_scope.parent:
|
|
return reachable & _break_check(evaluator, stmt, base_scope, element_scope.parent)
|
|
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
|