Format: text/x-python
⬇ Download File (8.3 KB)
   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