Format: text/x-python
⬇ Download File (4.5 KB)
   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
 126