flow_analysis.py 4.5 KB

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