1 """ 2 Type 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 15 The actual algorithm is based on a principle I call lazy type inference. That 16 said, the typical entry point for static analysis is calling 17 ``infer_expr_stmt``. There's separate logic for autocompletion in the API, the 18 inference_state is all about inferring an expression. 19 20 TODO this paragraph is not what jedi does anymore, it's similar, but not the 21 same. 22 23 Now you need to understand what follows after ``infer_expr_stmt``. Let's 24 make an example:: 25 26 import datetime 27 datetime.date.toda# <-- cursor here 28 29 First of all, this module doesn't care about completion. It really just cares 30 about ``datetime.date``. At the end of the procedure ``infer_expr_stmt`` will 31 return the ``date`` class. 32 33 To *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 43 Now what would happen if we wanted ``datetime.date.foo.bar``? Two more 44 calls to ``find_types``. However the second call would be ignored, because the 45 first one would return nothing (there's no foo attribute in ``date``). 46 47 What if the import would contain another ``ExprStmt`` like this:: 48 49 from foo import bar 50 Date = bar.baz 51 52 Well... You get it. Just another ``infer_expr_stmt`` recursion. It's really 53 easy. Python can obviously get way more complicated then this. To understand 54 tuple assignments, list comprehensions and everything else, a lot more code had 55 to be written. 56 57 Jedi has been tested very well, so you can just start modifying code. It's best 58 to write your own test first for your "new" feature. Don't be scared of 59 breaking stuff. As long as the tests pass, you're most likely to be fine. 60 61 I need to mention now that lazy type inference is really good because it 62 only *inferes* what needs to be *inferred*. All the statements and modules 63 that are not used are just being ignored. 64 """ 65 import parso 66 from jedi.file_io import FileIO 67 68 from jedi import debug 69 from jedi import settings 70 from jedi.inference import imports 71 from jedi.inference import recursion 72 from jedi.inference.cache import inference_state_function_cache 73 from jedi.inference import helpers 74 from jedi.inference.names import TreeNameDefinition 75 from jedi.inference.base_value import ContextualizedNode, \ 76 ValueSet, iterate_values 77 from jedi.inference.value import ClassValue, FunctionValue 78 from jedi.inference.syntax_tree import infer_expr_stmt, \ 79 check_tuple_assignments, tree_name_to_values 80 from jedi.inference.imports import follow_error_node_imports_if_possible 81 from jedi.plugins import plugin_manager 82 83 84 class 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.13') 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.do_dynamic_params_search = settings.dynamic_params 103 self.is_analysis = False 104 self.project = project 105 self.access_cache = {} 106 self.allow_unsafe_executions = False 107 self.flow_analysis_enabled = True 108 109 self.reset_recursion_limitations() 110 111 def import_module(self, import_names, sys_path=None, prefer_stubs=True): 112 return imports.import_module_by_names( 113 self, import_names, sys_path, prefer_stubs=prefer_stubs) 114 115 @staticmethod 116 @plugin_manager.decorate() 117 def execute(value, arguments): 118 debug.dbg('execute: %s %s', value, arguments) 119 with debug.increase_indent_cm(): 120 value_set = value.py__call__(arguments=arguments) 121 debug.dbg('execute result: %s in %s', value_set, value) 122 return value_set 123 124 # mypy doesn't suppport decorated propeties (https://github.com/python/mypy/issues/1362) 125 @property # type: ignore[misc] 126 @inference_state_function_cache() 127 def builtins_module(self): 128 module_name = 'builtins' 129 builtins_module, = self.import_module((module_name,), sys_path=[]) 130 return builtins_module 131 132 @property # type: ignore[misc] 133 @inference_state_function_cache() 134 def typing_module(self): 135 typing_module, = self.import_module(('typing',)) 136 return typing_module 137 138 def reset_recursion_limitations(self): 139 self.recursion_detector = recursion.RecursionDetector() 140 self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self) 141 142 def get_sys_path(self, **kwargs): 143 """Convenience function""" 144 return self.project._get_sys_path(self, **kwargs) 145 146 def infer(self, context, name): 147 def_ = name.get_definition(import_name_always=True) 148 if def_ is not None: 149 type_ = def_.type 150 is_classdef = type_ == 'classdef' 151 if is_classdef or type_ == 'funcdef': 152 if is_classdef: 153 c = ClassValue(self, context, name.parent) 154 else: 155 c = FunctionValue.from_context(context, name.parent) 156 return ValueSet([c]) 157 158 if type_ == 'expr_stmt': 159 is_simple_name = name.parent.type not in ('power', 'trailer') 160 if is_simple_name: 161 return infer_expr_stmt(context, def_, name) 162 if type_ == 'for_stmt': 163 container_types = context.infer_node(def_.children[3]) 164 cn = ContextualizedNode(context, def_.children[3]) 165 for_types = iterate_values(container_types, cn) 166 n = TreeNameDefinition(context, name) 167 return check_tuple_assignments(n, for_types) 168 if type_ in ('import_from', 'import_name'): 169 return imports.infer_import(context, name) 170 if type_ == 'with_stmt': 171 return tree_name_to_values(self, context, name) 172 elif type_ == 'param': 173 return context.py__getattribute__(name.value, position=name.end_pos) 174 elif type_ == 'namedexpr_test': 175 return context.infer_node(def_) 176 else: 177 result = follow_error_node_imports_if_possible(context, name) 178 if result is not None: 179 return result 180 181 return helpers.infer_call_of_leaf(context, name) 182 183 def parse_and_get_code(self, code=None, path=None, 184 use_latest_grammar=False, file_io=None, **kwargs): 185 if code is None: 186 if file_io is None: 187 file_io = FileIO(path) 188 code = file_io.read() 189 # We cannot just use parso, because it doesn't use errors='replace'. 190 code = parso.python_bytes_to_unicode(code, encoding='utf-8', errors='replace') 191 192 if len(code) > settings._cropped_file_size: 193 code = code[:settings._cropped_file_size] 194 195 grammar = self.latest_grammar if use_latest_grammar else self.grammar 196 return grammar.parse(code=code, path=path, file_io=file_io, **kwargs), code 197 198 def parse(self, *args, **kwargs): 199 return self.parse_and_get_code(*args, **kwargs)[0] 200