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