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 reachability_check(context, context_scope, node, origin_scope=None): flow_scope = node.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 flow_scope == origin_scope: #return REACHABLE #origin_scope = origin_scope.parent return _break_check(context, context_scope, flow_scope, node) def _break_check(context, context_scope, flow_scope, node): reachable = REACHABLE if flow_scope.type == 'if_stmt': if flow_scope.node_after_else(node): for check_node in flow_scope.check_nodes(): reachable = _check_if(context, check_node) if reachable in (REACHABLE, UNSURE): break reachable = reachable.invert() else: node = flow_scope.node_in_which_check_node(node) if node is not None: reachable = _check_if(context, node) elif flow_scope.type in ('try_stmt', 'while_stmt'): return UNSURE # Only reachable branches need to be examined further. if reachable in (UNREACHABLE, UNSURE): return reachable # TODO REMOVE WTF #if element_scope.type == 'file_input': # The definition is in another module and therefore just return what we # have generated. # return reachable if context_scope != flow_scope and context_scope != flow_scope.parent: flow_scope = flow_scope.get_parent_scope(include_flows=True) return reachable & _break_check(context, context_scope, flow_scope, node) else: return reachable def _check_if(context, node): types = context.eval_node(node) values = set(x.py__bool__() for x in types) if len(values) == 1: return Status.lookup_table[values.pop()] else: return UNSURE