Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jedi/inference/flow_analysis.py: 24%

85 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1from typing import Dict, Optional 

2 

3from jedi.parser_utils import get_flow_branch_keyword, is_scope, get_parent_scope 

4from jedi.inference.recursion import execution_allowed 

5from jedi.inference.helpers import is_big_annoying_library 

6 

7 

8class 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 

34REACHABLE = Status(True, 'reachable') 

35UNREACHABLE = Status(False, 'unreachable') 

36UNSURE = Status(None, 'unsure') 

37 

38 

39def _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 

47def 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 

88def _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 

115def _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