Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jedi-0.18.2-py3.8.egg/jedi/inference/__init__.py: 55%

107 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:56 +0000

1""" 

2Type inference of Python code in |jedi| is based on three assumptions: 

3 

4* The code uses as least side effects as possible. Jedi understands certain 

5 list/tuple/set modifications, but there's no guarantee that Jedi detects 

6 everything (list.append in different modules for example). 

7* No magic is being used: 

8 

9 - metaclasses 

10 - ``setattr()`` / ``__import__()`` 

11 - writing to ``globals()``, ``locals()``, ``object.__dict__`` 

12* The programmer is not a total dick, e.g. like `this 

13 <https://github.com/davidhalter/jedi/issues/24>`_ :-) 

14 

15The actual algorithm is based on a principle I call lazy type inference. That 

16said, the typical entry point for static analysis is calling 

17``infer_expr_stmt``. There's separate logic for autocompletion in the API, the 

18inference_state is all about inferring an expression. 

19 

20TODO this paragraph is not what jedi does anymore, it's similar, but not the 

21same. 

22 

23Now you need to understand what follows after ``infer_expr_stmt``. Let's 

24make an example:: 

25 

26 import datetime 

27 datetime.date.toda# <-- cursor here 

28 

29First of all, this module doesn't care about completion. It really just cares 

30about ``datetime.date``. At the end of the procedure ``infer_expr_stmt`` will 

31return the ``date`` class. 

32 

33To *visualize* this (simplified): 

34 

35- ``InferenceState.infer_expr_stmt`` doesn't do much, because there's no assignment. 

36- ``Context.infer_node`` cares for resolving the dotted path 

37- ``InferenceState.find_types`` searches for global definitions of datetime, which 

38 it finds in the definition of an import, by scanning the syntax tree. 

39- Using the import logic, the datetime module is found. 

40- Now ``find_types`` is called again by ``infer_node`` to find ``date`` 

41 inside the datetime module. 

42 

43Now what would happen if we wanted ``datetime.date.foo.bar``? Two more 

44calls to ``find_types``. However the second call would be ignored, because the 

45first one would return nothing (there's no foo attribute in ``date``). 

46 

47What if the import would contain another ``ExprStmt`` like this:: 

48 

49 from foo import bar 

50 Date = bar.baz 

51 

52Well... You get it. Just another ``infer_expr_stmt`` recursion. It's really 

53easy. Python can obviously get way more complicated then this. To understand 

54tuple assignments, list comprehensions and everything else, a lot more code had 

55to be written. 

56 

57Jedi has been tested very well, so you can just start modifying code. It's best 

58to write your own test first for your "new" feature. Don't be scared of 

59breaking stuff. As long as the tests pass, you're most likely to be fine. 

60 

61I need to mention now that lazy type inference is really good because it 

62only *inferes* what needs to be *inferred*. All the statements and modules 

63that are not used are just being ignored. 

64""" 

65import parso 

66from jedi.file_io import FileIO 

67 

68from jedi import debug 

69from jedi import settings 

70from jedi.inference import imports 

71from jedi.inference import recursion 

72from jedi.inference.cache import inference_state_function_cache 

73from jedi.inference import helpers 

74from jedi.inference.names import TreeNameDefinition 

75from jedi.inference.base_value import ContextualizedNode, \ 

76 ValueSet, iterate_values 

77from jedi.inference.value import ClassValue, FunctionValue 

78from jedi.inference.syntax_tree import infer_expr_stmt, \ 

79 check_tuple_assignments, tree_name_to_values 

80from jedi.inference.imports import follow_error_node_imports_if_possible 

81from jedi.plugins import plugin_manager 

82 

83 

84class InferenceState: 

85 def __init__(self, project, environment=None, script_path=None): 

86 if environment is None: 

87 environment = project.get_environment() 

88 self.environment = environment 

89 self.script_path = script_path 

90 self.compiled_subprocess = environment.get_inference_state_subprocess(self) 

91 self.grammar = environment.get_grammar() 

92 

93 self.latest_grammar = parso.load_grammar(version='3.11') 

94 self.memoize_cache = {} # for memoize decorators 

95 self.module_cache = imports.ModuleCache() # does the job of `sys.modules`. 

96 self.stub_module_cache = {} # Dict[Tuple[str, ...], Optional[ModuleValue]] 

97 self.compiled_cache = {} # see `inference.compiled.create()` 

98 self.inferred_element_counts = {} 

99 self.mixed_cache = {} # see `inference.compiled.mixed._create()` 

100 self.analysis = [] 

101 self.dynamic_params_depth = 0 

102 self.is_analysis = False 

103 self.project = project 

104 self.access_cache = {} 

105 self.allow_descriptor_getattr = False 

106 self.flow_analysis_enabled = True 

107 

108 self.reset_recursion_limitations() 

109 

110 def import_module(self, import_names, sys_path=None, prefer_stubs=True): 

111 return imports.import_module_by_names( 

112 self, import_names, sys_path, prefer_stubs=prefer_stubs) 

113 

114 @staticmethod 

115 @plugin_manager.decorate() 

116 def execute(value, arguments): 

117 debug.dbg('execute: %s %s', value, arguments) 

118 with debug.increase_indent_cm(): 

119 value_set = value.py__call__(arguments=arguments) 

120 debug.dbg('execute result: %s in %s', value_set, value) 

121 return value_set 

122 

123 # mypy doesn't suppport decorated propeties (https://github.com/python/mypy/issues/1362) 

124 @property # type: ignore[misc] 

125 @inference_state_function_cache() 

126 def builtins_module(self): 

127 module_name = 'builtins' 

128 builtins_module, = self.import_module((module_name,), sys_path=[]) 

129 return builtins_module 

130 

131 @property # type: ignore[misc] 

132 @inference_state_function_cache() 

133 def typing_module(self): 

134 typing_module, = self.import_module(('typing',)) 

135 return typing_module 

136 

137 def reset_recursion_limitations(self): 

138 self.recursion_detector = recursion.RecursionDetector() 

139 self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self) 

140 

141 def get_sys_path(self, **kwargs): 

142 """Convenience function""" 

143 return self.project._get_sys_path(self, **kwargs) 

144 

145 def infer(self, context, name): 

146 def_ = name.get_definition(import_name_always=True) 

147 if def_ is not None: 

148 type_ = def_.type 

149 is_classdef = type_ == 'classdef' 

150 if is_classdef or type_ == 'funcdef': 

151 if is_classdef: 

152 c = ClassValue(self, context, name.parent) 

153 else: 

154 c = FunctionValue.from_context(context, name.parent) 

155 return ValueSet([c]) 

156 

157 if type_ == 'expr_stmt': 

158 is_simple_name = name.parent.type not in ('power', 'trailer') 

159 if is_simple_name: 

160 return infer_expr_stmt(context, def_, name) 

161 if type_ == 'for_stmt': 

162 container_types = context.infer_node(def_.children[3]) 

163 cn = ContextualizedNode(context, def_.children[3]) 

164 for_types = iterate_values(container_types, cn) 

165 n = TreeNameDefinition(context, name) 

166 return check_tuple_assignments(n, for_types) 

167 if type_ in ('import_from', 'import_name'): 

168 return imports.infer_import(context, name) 

169 if type_ == 'with_stmt': 

170 return tree_name_to_values(self, context, name) 

171 elif type_ == 'param': 

172 return context.py__getattribute__(name.value, position=name.end_pos) 

173 elif type_ == 'namedexpr_test': 

174 return context.infer_node(def_) 

175 else: 

176 result = follow_error_node_imports_if_possible(context, name) 

177 if result is not None: 

178 return result 

179 

180 return helpers.infer_call_of_leaf(context, name) 

181 

182 def parse_and_get_code(self, code=None, path=None, 

183 use_latest_grammar=False, file_io=None, **kwargs): 

184 if code is None: 

185 if file_io is None: 

186 file_io = FileIO(path) 

187 code = file_io.read() 

188 # We cannot just use parso, because it doesn't use errors='replace'. 

189 code = parso.python_bytes_to_unicode(code, encoding='utf-8', errors='replace') 

190 

191 if len(code) > settings._cropped_file_size: 

192 code = code[:settings._cropped_file_size] 

193 

194 grammar = self.latest_grammar if use_latest_grammar else self.grammar 

195 return grammar.parse(code=code, path=path, file_io=file_io, **kwargs), code 

196 

197 def parse(self, *args, **kwargs): 

198 return self.parse_and_get_code(*args, **kwargs)[0]