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]