| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- from typing import Dict, Optional
- from jedi.parser_utils import get_flow_branch_keyword, is_scope, get_parent_scope
- from jedi.inference.recursion import execution_allowed
- from jedi.inference.helpers import is_big_annoying_library
- class Status:
- lookup_table: Dict[Optional[bool], 'Status'] = {}
- def __init__(self, value: Optional[bool], name: str) -> None:
- 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 _get_flow_scopes(node):
- while True:
- node = get_parent_scope(node, include_flows=True)
- if node is None or is_scope(node):
- return
- yield node
- def reachability_check(context, value_scope, node, origin_scope=None):
- if is_big_annoying_library(context) \
- or not context.inference_state.flow_analysis_enabled:
- return UNSURE
- first_flow_scope = get_parent_scope(node, include_flows=True)
- if origin_scope is not None:
- origin_flow_scopes = list(_get_flow_scopes(origin_scope))
- node_flow_scopes = list(_get_flow_scopes(node))
- branch_matches = True
- for flow_scope in origin_flow_scopes:
- if flow_scope in node_flow_scopes:
- node_keyword = get_flow_branch_keyword(flow_scope, node)
- origin_keyword = get_flow_branch_keyword(flow_scope, origin_scope)
- branch_matches = node_keyword == origin_keyword
- if flow_scope.type == 'if_stmt':
- if not branch_matches:
- return UNREACHABLE
- elif flow_scope.type == 'try_stmt':
- if not branch_matches and origin_keyword == 'else' \
- and node_keyword == 'except':
- return UNREACHABLE
- if branch_matches:
- break
- # 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 first_flow_scope == origin_scope and branch_matches:
- return REACHABLE
- origin_scope = origin_scope.parent
- return _break_check(context, value_scope, first_flow_scope, node)
- def _break_check(context, value_scope, flow_scope, node):
- reachable = REACHABLE
- if flow_scope.type == 'if_stmt':
- if flow_scope.is_node_after_else(node):
- for check_node in flow_scope.get_test_nodes():
- reachable = _check_if(context, check_node)
- if reachable in (REACHABLE, UNSURE):
- break
- reachable = reachable.invert()
- else:
- flow_node = flow_scope.get_corresponding_test_node(node)
- if flow_node is not None:
- reachable = _check_if(context, flow_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
- if value_scope != flow_scope and value_scope != flow_scope.parent:
- flow_scope = get_parent_scope(flow_scope, include_flows=True)
- return reachable & _break_check(context, value_scope, flow_scope, node)
- else:
- return reachable
- def _check_if(context, node):
- with execution_allowed(context.inference_state, node) as allowed:
- if not allowed:
- return UNSURE
- types = context.infer_node(node)
- values = set(x.py__bool__() for x in types)
- if len(values) == 1:
- return Status.lookup_table[values.pop()]
- else:
- return UNSURE
|