Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/jedi/inference/__init__.py: 34%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

122 statements  

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""" 

65from typing import Any 

66 

67import parso 

68from jedi.file_io import FileIO 

69 

70from jedi import debug 

71from jedi import settings 

72from jedi.inference import imports 

73from jedi.inference import recursion 

74from jedi.inference.cache import inference_state_function_cache 

75from jedi.inference import helpers 

76from jedi.inference.names import TreeNameDefinition 

77from jedi.inference.base_value import ContextualizedNode, \ 

78 ValueSet, iterate_values 

79from jedi.inference.value import ClassValue, FunctionValue 

80from jedi.inference.syntax_tree import infer_expr_stmt, \ 

81 check_tuple_assignments, tree_name_to_values 

82from jedi.inference.imports import follow_error_node_imports_if_possible 

83from jedi.plugins import plugin_manager 

84 

85 

86class InferenceState: 

87 analysis_modules: "list[Any]" 

88 

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

90 if environment is None: 

91 environment = project.get_environment() 

92 self.environment = environment 

93 self.script_path = script_path 

94 self.compiled_subprocess = environment.get_inference_state_subprocess(self) 

95 self.grammar = environment.get_grammar() 

96 

97 self.latest_grammar = parso.load_grammar(version='3.13') 

98 self.memoize_cache = {} # for memoize decorators 

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

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

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

102 self.inferred_element_counts = {} 

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

104 self.analysis = [] 

105 self.dynamic_params_depth = 0 

106 self.do_dynamic_params_search = settings.dynamic_params 

107 self.is_analysis = False 

108 self.project = project 

109 self.access_cache = {} 

110 self.allow_unsafe_executions = False 

111 self.flow_analysis_enabled = True 

112 

113 self.reset_recursion_limitations() 

114 

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

116 return imports.import_module_by_names( 

117 self, import_names, sys_path, prefer_stubs=prefer_stubs) 

118 

119 @staticmethod 

120 @plugin_manager.decorate() 

121 def execute(value, arguments): 

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

123 with debug.increase_indent_cm(): 

124 value_set = value.py__call__(arguments=arguments) 

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

126 return value_set 

127 

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

129 @property 

130 @inference_state_function_cache() 

131 def builtins_module(self): 

132 module_name = 'builtins' 

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

134 return builtins_module 

135 

136 @property 

137 @inference_state_function_cache() 

138 def typing_module(self): 

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

140 return typing_module 

141 

142 @property 

143 @inference_state_function_cache() 

144 def types_module(self): 

145 typing_module, = self.import_module(('types',)) 

146 return typing_module 

147 

148 @inference_state_function_cache() 

149 def typing_tuple(self): 

150 return self.typing_module.py__getattribute__("Tuple") 

151 

152 @inference_state_function_cache() 

153 def typing_type(self): 

154 return self.typing_module.py__getattribute__("Type") 

155 

156 def reset_recursion_limitations(self): 

157 self.recursion_detector = recursion.RecursionDetector() 

158 self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self) 

159 

160 def get_sys_path(self, **kwargs): 

161 """Convenience function""" 

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

163 

164 def infer(self, context, name): 

165 def_ = name.get_definition(import_name_always=True) 

166 if def_ is not None: 

167 type_ = def_.type 

168 is_classdef = type_ == 'classdef' 

169 if is_classdef or type_ == 'funcdef': 

170 if is_classdef: 

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

172 else: 

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

174 return ValueSet([c]) 

175 

176 if type_ == 'expr_stmt': 

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

178 if is_simple_name: 

179 return infer_expr_stmt(context, def_, name) 

180 if type_ == 'for_stmt': 

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

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

183 for_types = iterate_values(container_types, cn) 

184 n = TreeNameDefinition(context, name) 

185 return check_tuple_assignments(n, for_types) 

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

187 return imports.infer_import(context, name) 

188 if type_ == 'with_stmt': 

189 return tree_name_to_values(self, context, name) 

190 elif type_ == 'param': 

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

192 elif type_ == 'namedexpr_test': 

193 return context.infer_node(def_) 

194 else: 

195 result = follow_error_node_imports_if_possible(context, name) 

196 if result is not None: 

197 return result 

198 

199 return helpers.infer_call_of_leaf(context, name) 

200 

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

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

203 if code is None: 

204 if file_io is None: 

205 file_io = FileIO(path) 

206 code = file_io.read() 

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

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

209 

210 if len(code) > settings._cropped_file_size: 

211 code = code[:settings._cropped_file_size] 

212 

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

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

215 

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

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